[net.sources] Ispell: Interactive spelling checker

buehring@ti-csl.CSNET (Walt Buehring) (01/12/87)

Ispell is a screen oriented spelling checker that offers possible
corrections for each misspelled word encountered.  Also included is a
GNU EMACS interface.  It is not perfect but it provides the best user
interface of any UNIX speller I have seen (those of you familiar with
TOPS20 spellers will feel right at home).

The dictionary format is incompatible with /usr/dict/words.  It allows
one to specify the legal suffixes for each word and thus requires less
guesswork on the part of the speller.  The dictionary provided (in a
separate posting) has ~15K words, not including suffixes.  This seems
a bit small but has rarely presented a problem in actual use, perhaps
due to clever use of suffix flags.

I did not write this program (but did check with the author about
posting it) and do not plan to maintain/fix/enhance it.  I do not know
which systems it runs on besides Berkeley 4.2 (actually Ultrix).  I
will attempt to post dictionary diffs periodically -- see the README
file for more info.

This is my first source posting... hope I get it right.

Walt Buehring
Texas Instruments - Computer Science Center

ARPA:  Buehring%TI-CSL@CSNet-Relay
UUCP:  {smu, texsun, im4u, rice} ! ti-csl ! buehring

No warranty expressed or implied.  All sales final.  Void where prohibited.
Batteries not included.  If you don't like it, change it.

		o /		o /		o /		o /
-----------------X---------------X---------------X---------------X----
		o \		o \		o \		o \

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	ispell.man
#	README
#	Makefile
#	buildhash.c
#	good.c
#	hash.c
#	ispell.c
#	ispell.el
#	ispell.h
#	lookup.c
#	term.c
#	tree.c
# This archive created: Mon Jan 12 06:22:16 1987
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'ispell.man'
then
	echo shar: "will not over-write existing file 'ispell.man'"
else
cat << \SHAR_EOF > 'ispell.man'
.\" -*- Mode:Text -*-
.TH ISPELL local MIT
.SH NAME
ispell \- Correct spelling for a file
.SH SYNOPSIS
.B ispell
[
file |
.B \-a
|
.B \-l
]
.SH DESCRIPTION
.PP
.I Ispell
is fashioned after the
.I spell
program from ITS (called
.I ispell
on Twenex systems.)  The most common usage is "ispell filename".  In this
case,
.I ispell
will display each word which does not appear in the dictionary, and
allow you to change it.  If there are "near misses" in the dictionary
(words which differ by only a single letter, a missing or extra letter,
or a pair of transposed letters), then they are also displayed.  If you
think the word is correct as it stands, you can type either "Space" to
accept it this one time, or "I" to accept it and put it in your private
dictionary.  If one of the near misses is the word you want, type the
corresponding number.  Finally, if none of these choices is right, you
can type "R" and you will be prompted for a replacement word.
.PP
When a misspelled word is found, it is printed at the top of the screen.
Any near misses will be printed on the following lines, and finally, two
lines containing the word are printed at the bottom of the screen.  If
your terminal can type in reverse video, the word itself is highlighted.
.PP
The
.B \-l
or "list" option to
.I ispell
is used to produce a list of misspelled words from the standard input.
.PP
The
.B \-a
is intended to be used from other programs through a pipe.  In this
mode,
.I ispell
expects the standard input to consist of single words.  Each word is
read, and a single line is written to the standard output.  If the word
was found in the main dictionary, or your personal dictionary, then the
line contains only a '*'.  If the word was found through suffix removal,
then the line contains a '+', a space, and the root word.  If the word
is not in the dictionary, but there are near misses, then the line
contains an '&', a space, and a list of the near misses separated by
spaces.  Also, each near miss is capitalized the same as the input
words.  Finally, if the word neither appears in the dictionary, and
there are no near misses, then the line contains only a '#'.  This mode
is also suitable for interactive use when you want to figure out the
spelling of a single word.  (These characters are the same as the codes
that the real spell program uses.)
.SH FILES
/usr/local/lib/ispell.hash
.br
$HOME/ispell.words
.SH BUGS
It takes about five seconds for
.I ispell
to read in the hash table.
.sp
Perhaps more than ten choices should be allowed for near misses.
.sp
The hash table is stored as a quarter-megabyte array, so a PDP-11
version does not seem likely.
.sp
.I Ispell
should understand more
.I troff
syntax, and deal more intelligently with contractions.
.SH AUTHOR
Pace Willisson (pace@mit-vax)
SHAR_EOF
if test 2789 -ne "`wc -c < 'ispell.man'`"
then
	echo shar: "error transmitting 'ispell.man'" '(should have been 2789 characters)'
fi
fi
if test -f 'README'
then
	echo shar: "will not over-write existing file 'README'"
else
cat << \SHAR_EOF > 'README'
-*- Mode:Text -*-

Ispell consists of two programs: the actual spelling checker, "ispell",
and the hash table builder, "buildhash".  Everything is set up so you
can just say "make install" and have everything happen.  You might want
to edit the makefile, and ispell.h to change the destination of the
program and the hash table.

The dictionary comes from the ITS spell dictionary.  I got it from
"ml:wba;dict 191", although I don't know that this is the copy currenty
in use on the 20's around MIT.

----------------------------------------------------------------------

Addendum:

My eternal gratitude to the author of ispell -- I don't know how I
ever lived without it.  I received his permission to post ispell to
the net and have added a GNU EMACS interface.  Look in the file
ispell.el for installation instructions.

As far as I know, no one informally "supports" this program.  If you
would like to "adopt" it (collect fixes/enhancements and post a new
version periodically), feel free to do so.

I volunteer to collect dictionary diffs and post a composite diff
periodically.  If you add a lot of words to the main dictionary, send
me the diffs between the the modified dictionary and the posted one.
Also, if you have access to a TOPS20 system with a more complete
dictionary in ispell format, send me the diffs if you can.  Just
PLEASE don't dump an entire dictionary to our site!

The dictionary posted is one I snarfed from around here -- after
comparison with the one originally supplied, ours appears a tad more
complete and accurate.

Walt Buehring
Texas Instruments - Computer Science Center

ARPA:  Buehring%TI-CSL@CSNet-Relay
UUCP:  {smu, texsun, im4u, rice} ! ti-csl ! buehring

----------------------------------------------------------------------

The following is the only documentation I could find about the format
of the dictionary.  It was written for the TOPS20 speller that ispell
mimics, so I believe most the information is applicable.  It should be
useful if you want to add words to the main dictionary by hand.  -WB

----------------------------------------------------------------------

11.6  Dictionary flags

     Words  in SPELL's main dictionary (but not the other dictionaries) may
have flags associated with  them  to  indicate  the  legality  of  suffixes
without  the  need  to keep the full suffixed words in the dictionary.  The
flags have "names" consisting of single  letters.    Their  meaning  is  as
follows:

Let  #  and  @  be  "variables"  that can stand for any letter.  Upper case
letters are constants.  "..."  stands  for  any  string  of  zero  or  more
letters,  but note that no word may exist in the dictionary which is not at
least 2 letters long, so, for example, FLY may not be produced  by  placing
the  "Y"  flag  on "F".  Also, no flag is effective unless the word that it
creates is at least 4 letters  long,  so,  for  example,  WED  may  not  be
produced by placing the "D" flag on "WE".

"V" flag:
        ...E --> ...IVE  as in CREATE --> CREATIVE
        if # .ne. E, ...# --> ...#IVE  as in PREVENT --> PREVENTIVE

"N" flag:
        ...E --> ...ION  as in CREATE --> CREATION
        ...Y --> ...ICATION  as in MULTIPLY --> MULTIPLICATION
        if # .ne. E or Y, ...# --> ...#EN  as in FALL --> FALLEN

"X" flag:
        ...E --> ...IONS  as in CREATE --> CREATIONS
        ...Y --> ...ICATIONS  as in MULTIPLY --> MULTIPLICATIONS
        if # .ne. E or Y, ...# --> ...#ENS  as in WEAK --> WEAKENS

"H" flag:
        ...Y --> ...IETH  as in TWENTY --> TWENTIETH
        if # .ne. Y, ...# --> ...#TH  as in HUNDRED --> HUNDREDTH

"Y" FLAG:
        ... --> ...LY  as in QUICK --> QUICKLY

"G" FLAG:
        ...E --> ...ING  as in FILE --> FILING
        if # .ne. E, ...# --> ...#ING  as in CROSS --> CROSSING

"J" FLAG"
        ...E --> ...INGS  as in FILE --> FILINGS
        if # .ne. E, ...# --> ...#INGS  as in CROSS --> CROSSINGS

"D" FLAG:
        ...E --> ...ED  as in CREATE --> CREATED
        if @ .ne. A, E, I, O, or U,
                ...@Y --> ...@IED  as in IMPLY --> IMPLIED
        if # .ne. E or Y, or (# = Y and @ = A, E, I, O, or U)
                ...@# --> ...@#ED  as in CROSS --> CROSSED
                                or CONVEY --> CONVEYED
"T" FLAG:
        ...E --> ...EST  as in LATE --> LATEST
        if @ .ne. A, E, I, O, or U,
                ...@Y --> ...@IEST  as in DIRTY --> DIRTIEST
        if # .ne. E or Y, or (# = Y and @ = A, E, I, O, or U)
                ...@# --> ...@#EST  as in SMALL --> SMALLEST
                                or GRAY --> GRAYEST

"R" FLAG:
        ...E --> ...ER  as in SKATE --> SKATER
        if @ .ne. A, E, I, O, or U,
                ...@Y --> ...@IER  as in MULTIPLY --> MULTIPLIER
        if # .ne. E or Y, or (# = Y and @ = A, E, I, O, or U)
                ...@# --> ...@#ER  as in BUILD --> BUILDER
                                or CONVEY --> CONVEYER

"Z FLAG:
        ...E --> ...ERS  as in SKATE --> SKATERS
        if @ .ne. A, E, I, O, or U,
                ...@Y --> ...@IERS  as in MULTIPLY --> MULTIPLIERS
        if # .ne. E or Y, or (# = Y and @ = A, E, I, O, or U)
                ...@# --> ...@#ERS  as in BUILD --> BUILDERS
                                or SLAY --> SLAYERS

"S" FLAG:
        if @ .ne. A, E, I, O, or U,
                ...@Y --> ...@IES  as in IMPLY --> IMPLIES
        if # .eq. S, X, Z, or H,
                ...# --> ...#ES  as in FIX --> FIXES
        if # .ne. S, X, Z, H, or Y, or (# = Y and @ = A, E, I, O, or U)
                ...@# --> ...@#S  as in BAT --> BATS
                                or CONVEY --> CONVEYS

"P" FLAG:
        if @ .ne. A, E, I, O, or U,
                ...@Y --> ...@INESS  as in CLOUDY --> CLOUDINESS
        if # .ne. Y, or @ = A, E, I, O, or U,
                ...@# --> ...@#NESS  as in LATE --> LATENESS
                                or GRAY --> GRAYNESS

"M" FLAG:
        ... --> ...'S  as in DOG --> DOG'S

----------------------------------------------------------------------

[Whew!  That's all very nice, but how about a quick reference...  -WB]

V -  ive
N -  ion, tion, en
X -  ions, ications, ens
H -  th, ieth
Y -  ly
G -  ing
J -  ings
D -  ed
T -  est
R -  er
Z -  ers
S -  s, es, ies
P -  ness, iness
M -  's
SHAR_EOF
if test 6256 -ne "`wc -c < 'README'`"
then
	echo shar: "error transmitting 'README'" '(should have been 6256 characters)'
fi
fi
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
# -*- Mode: Text -*-

CFLAGS = -O
BINDIR = /usr/local/bin
LIBDIR = /usr/local/lib

all: buildhash ispell ispell.hash

ispell.hash: buildhash dict.191
	buildhash

install: buildhash ispell ispell.hash
	cp ispell ${BINDIR}/ispell
	cp ispell.hash ${LIBDIR}/ispell.hash
	chmod 755 ${BINDIR}/ispell ${LIBDIR}/ispell.hash

buildhash: buildhash.o hash.o
	cc -o buildhash buildhash.o hash.o

ispell: ispell.o term.o good.o lookup.o hash.o tree.o
	cc $(CFLAGS) -o ispell ispell.o term.o good.o lookup.o \
		hash.o tree.o -ltermlib

clean:
	rm -f *.o buildhash ispell core a.out mon.out hash.out \
		stats.191 count.191
SHAR_EOF
if test 610 -ne "`wc -c < 'Makefile'`"
then
	echo shar: "error transmitting 'Makefile'" '(should have been 610 characters)'
fi
fi
if test -f 'buildhash.c'
then
	echo shar: "will not over-write existing file 'buildhash.c'"
else
cat << \SHAR_EOF > 'buildhash.c'
/* -*- Mode: Text -*- */
/*
 * buildhash.c - make a hash table for ispell
 *
 * Pace Willisson, 1983
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "ispell.h"

#define DICT "dict.191"
#define COUNT "count.191"
#define STATS "stats.191"

#define NSTAT 100
struct stat dstat, cstat;

int numwords, hashsize;

char *malloc();

struct dent *hashtbl;

main ()
{
	FILE *countf;
	FILE *statf;
	int stats[NSTAT];
	int i;

	if (stat (DICT, &dstat) < 0) {
		fprintf (stderr, "No dictionary (%s)\n", DICT);
		exit (1);
	}

	if (stat (COUNT, &cstat) < 0 || dstat.st_mtime > cstat.st_mtime)
		newcount ();

	if ((countf = fopen (COUNT, "r")) == NULL) {
		fprintf (stderr, "No count file\n");
		exit (1);
	}
	numwords = 0;
	fscanf (countf, "%d", &numwords);
	fclose (countf);
	if (numwords == 0) {
		fprintf (stderr, "Bad count file\n");
		exit (1);
	}
	hashsize = numwords;
	readdict ();

	if ((statf = fopen (STATS, "w")) == NULL) {
		fprintf (stderr, "Can't create %s\n", STATS);
		exit (1);
	}

	for (i = 0; i < NSTAT; i++)
		stats[i] = 0;
	for (i = 0; i < hashsize; i++) {
		struct dent *dp;
		int j;
		if (hashtbl[i].used == 0) {
			stats[0]++;
		} else {
			for (j = 1, dp = &hashtbl[i]; dp->next != NULL; j++, dp = dp->next)
				;
			if (j >= NSTAT)
				j = NSTAT - 1;
			stats[j]++;
		}
	}
	for (i = 0; i < NSTAT; i++)
		fprintf (statf, "%d: %d\n", i, stats[i]);
	fclose (statf);

	filltable ();

	output ();
}

output ()
{
	FILE *outfile;
	struct hashheader hashheader;
	int strptr, n, i;

	if ((outfile = fopen ("ispell.hash", "w")) == NULL) {
		fprintf (stderr, "can't create ispell.hash\n");
		return;
	}
	hashheader.magic = MAGIC;
	hashheader.stringsize = 0;
	hashheader.tblsize = hashsize;
	fwrite (&hashheader, sizeof hashheader, 1, outfile);
	strptr = 0;
	for (i = 0; i < hashsize; i++) {
		n = strlen (hashtbl[i].word) + 1;
		fwrite (hashtbl[i].word, n, 1, outfile);
		hashtbl[i].word = (char *)strptr;
		strptr += n;
	}
	for (i = 0; i < hashsize; i++) {
		if (hashtbl[i].next != 0) {
			int x;
			x = hashtbl[i].next - hashtbl;
			hashtbl[i].next = (struct dent *)x;
		} else {
			hashtbl[i].next = (struct dent *)-1;
		}
	}
	fwrite (hashtbl, sizeof (struct dent), hashsize, outfile);
	hashheader.stringsize = strptr;
	rewind (outfile);
	fwrite (&hashheader, sizeof hashheader, 1, outfile);
	fclose (outfile);
}

filltable ()
{
	struct dent *freepointer, *nextword, *dp;
	int i;

	for (freepointer = hashtbl; freepointer->used; freepointer++)
		;
	for (nextword = hashtbl, i = numwords; i != 0; nextword++, i--) {
		if (nextword->used == 0) {
			continue;
		}
		if (nextword->next == NULL) {
			continue;
		}
		if (nextword->next >= hashtbl && nextword->next < hashtbl + hashsize) {
			continue;
		}
		dp = nextword;
		while (dp->next) {
			if (freepointer > hashtbl + hashsize) {
				fprintf (stderr, "table overflow\n");
				getchar ();
				break;
			}
			*freepointer = *(dp->next);
			dp->next = freepointer;
			dp = freepointer;

			while (freepointer->used)
				freepointer++;
		}
	}
}


readdict ()
{
	struct dent d;
	char lbuf[100];
	FILE *dictf;
	int i;
	int h;
	char *p;

	if ((dictf = fopen (DICT, "r")) == NULL) {
		fprintf (stderr, "Can't open dictionary\n");
		exit (1);
	}

	hashtbl = (struct dent *) calloc (numwords, sizeof (struct dent));
	if (hashtbl == NULL) {
		fprintf (stderr, "couldn't allocate hash table\n");
		exit (1);
	}

	i = 0;
	while (fgets (lbuf, sizeof lbuf, dictf) != NULL) {
		if (i % 1000 == 0) {
			printf ("%d ", i);
			fflush (stdout);
		}
		i++;

		p = &lbuf [ strlen (lbuf) - 1 ];
		if (*p == '\n')
			*p = 0;

		if (makedent (lbuf, &d) < 0)
			continue;

		d.word = malloc (strlen (lbuf) + 1);
		if (d.word == NULL) {
			fprintf (stderr, "couldn't allocate space for word %s\n", lbuf);
			exit (1);
		}
		strcpy (d.word, lbuf);

		h = hash (lbuf, strlen (lbuf), hashsize);

		if (hashtbl[h].used == 0) {
			hashtbl[h] = d;

		} else {
			struct dent *dp;

			dp = (struct dent *) malloc (sizeof (struct dent));
			if (dp == NULL) {
				fprintf (stderr, "couldn't allocate space for collision\n");
				exit (1);
			}
			*dp = d;
			dp->next = hashtbl[h].next;
			hashtbl[h].next = dp;
		}
	}
	printf ("\n");
}

/*
 * fill in the flags in d, and put a null after the word in s
 */

makedent (lbuf, d)
char *lbuf;
struct dent *d;
{
	char *p, *index();

	d->next = NULL;
	d->used = 1;
	d->v_flag = 0;
	d->n_flag = 0;
	d->x_flag = 0;
	d->h_flag = 0;
	d->y_flag = 0;
	d->g_flag = 0;
	d->j_flag = 0;
	d->d_flag = 0;
	d->t_flag = 0;
	d->r_flag = 0;
	d->z_flag = 0;
	d->s_flag = 0;
	d->p_flag = 0;
	d->m_flag = 0;

	p = index (lbuf, '/');
	if (p != NULL)
		*p = 0;
	if (strlen (lbuf) > WORDLEN - 1) {
		printf ("%s: word too big\n");
		return (-1);
	}

	if (p == NULL)
		return (0);

	p++;
	while (*p != NULL) {
		switch (*p) {
		case 'V': d->v_flag = 1; break;
		case 'N': d->n_flag = 1; break;
		case 'X': d->x_flag = 1; break;
		case 'H': d->h_flag = 1; break;
		case 'Y': d->y_flag = 1; break;
		case 'G': d->g_flag = 1; break;
		case 'J': d->j_flag = 1; break;
		case 'D': d->d_flag = 1; break;
		case 'T': d->t_flag = 1; break;
		case 'R': d->r_flag = 1; break;
		case 'Z': d->z_flag = 1; break;
		case 'S': d->s_flag = 1; break;
		case 'P': d->p_flag = 1; break;
		case 'M': d->m_flag = 1; break;
		case 0:
 			fprintf (stderr, "no key word %s\n", lbuf);
			continue;
		default:
			fprintf (stderr, "unknown flag %c word %s\n", 
					*p, lbuf);
			break;
		}
		p++;
		if (*p != '/' && *p != NULL && *p != '\n') {
			fprintf (stderr, "bad format %s (%c 0%o)\n", 
					lbuf, *p, *p);
			break;
		}
		if (*p)
			p++;
	
	}
	return (0);
}

newcount ()
{
	char buf[200];
	FILE *d;
	int i;

	fprintf (stderr, "Counting words in dictionary ...\n");

	if ((d = fopen (DICT, "r")) == NULL) {
		fprintf (stderr, "Can't open dictionary\n");
		exit (1);
	}

	i = 0;
	while (fgets (buf, sizeof buf, d) != NULL) {
		i++;
		if (i % 1000 == 0) {
			printf ("%d ", i);
			fflush (stdout);
		}
	}
	fclose (d);
	printf ("\n%d words\n", i);
	if ((d = fopen (COUNT, "w")) == NULL) {
		fprintf (stderr, "can't create %s\n", COUNT);
		exit (1);
	}
	fprintf (d, "%d\n", i);
	fclose (d);
}
SHAR_EOF
if test 6141 -ne "`wc -c < 'buildhash.c'`"
then
	echo shar: "error transmitting 'buildhash.c'" '(should have been 6141 characters)'
fi
fi
if test -f 'good.c'
then
	echo shar: "will not over-write existing file 'good.c'"
else
cat << \SHAR_EOF > 'good.c'
/* -*- Mode:Text -*- */
/*
 * good.c - see if a word or its root word
 * is in the dictionary.
 *
 * Pace Willisson, 1983
 */

#include <stdio.h>
#include <ctype.h>
#include "ispell.h"

struct dent *lookup();

static int wordok;


good (w)
register char *w;
{
	char nword[100];
	register char *p, *q;
	register n;

	for (p = w, q = nword; *p; p++, q++) {
		if (islower (*p))
			*q = toupper (*p);
		else
			*q = *p;
	}
	*q = 0;

	rootword[0] = 0;

	if (lookup (nword, strlen (nword)) != NULL) {
		return (1);
	}

	/* try stripping off suffixes */

	n = strlen (w);
	if (n == 1)
		return (1);

	if (n < 4)
		return (treelookup (w));

	wordok = 0;

	/* this part from 'check.mid' */
	switch (nword [ strlen (nword) - 1 ]) {
	case 'D': d_ending (nword); break;	/* FOR "CREATED", "IMPLIED", "CROSSED" */
	case 'T': t_ending (nword); break;	/* FOR "LATEST", "DIRTIEST", "BOLDEST" */
	case 'R': r_ending (nword); break;	/* FOR "LATER", "DIRTIER", "BOLDER" */
	case 'G': g_ending (nword); break;	/* FOR "CREATING", "FIXING" */
	case 'H': h_ending (nword); break;	/* FOR "HUNDREDTH", "TWENTIETH" */
	case 'S': s_ending (nword); break;	/* FOR ALL SORTS OF THINGS ENDING IN "S" */
	case 'N': n_ending (nword); break;	/* "TIGHTEN", "CREATION", "MULIPLICATION" */
	case 'E': e_ending (nword); break;	/* FOR "CREATIVE", "PREVENTIVE" */
	case 'Y': y_ending (nword); break;	/* FOR "QUICKLY" */
	default:
		break;
	}
	
	if (wordok) {
		strcpy (rootword, &hashstrings [ (int)(lastdent->word) ]);
	} else {
		wordok = treelookup (w); /* rootword shouldn't be set in this case */
	}
	return (wordok);

}


g_ending (w)
char *w;
{
	char *p;
	struct dent *dent;

	p = w + strlen (w) - 3;	/* if the word ends in 'ing', then *p == 'i' */
	
	if (strcmp (p, "ING") != 0)
		return;

	*p = 'E';	/* change I to E, like in CREATING */
	*(p+1) = 0;

	if (strlen (w) < 2)
		return;

	if ((dent = lookup (w, strlen (w))) != NULL) {
		if (dent->g_flag) 
			wordok = 1;
		return;
	}


	*p = 0;

	if (strlen (w) < 2)
		return;

	if (p[-1] == 'E')
		return;	/* this stops CREATEING */

	if (strlen (w) < 2)
		return;

	if ((dent = lookup (w, strlen (w))) != NULL) {
		if (dent->g_flag)
			wordok = 1;
		return;
	}
	return;
}

d_ending (w)
char *w;
{
	char *p;
	struct dent *dent;

	p = w + strlen (w) - 2;

	if (strcmp (p, "ED") != 0)
		return;

	p[1] = 0;	/* kill 'D' */

	if ((dent = lookup (w, strlen (w))) != NULL) {	/* like CREATED */
		if (dent->d_flag)
			wordok = 1;
		return;
	}

	if (strlen (w) < 3)
		return;

	p[0] = 0;
	p--;

	/* ED is now completely gone */

	if (p[0] == 'I' && !vowel (p[-1])) {
		p[0] = 'Y';
		if ((dent = lookup (w, strlen (w))) != NULL) {
			if (dent->d_flag)
				wordok = 1;
			return;
		}
	}

	if ((p[0] != 'E' && p[0] != 'Y') ||
	    (p[0] == 'Y' && vowel (p[-1]))) {
		if ((dent = lookup (w, strlen (w))) != NULL) {
			if (dent->d_flag)
				wordok = 1;
			return;
		}
	}
}

t_ending (w)
char *w;
{

	char *p;
	struct dent *dent;

	p = w + strlen (w) - 3;

	if (strcmp (p, "EST") != 0)
		return;

	p[1] = 0;	/* kill 'S' */

	if ((dent = lookup (w, strlen (w))) != NULL) {
		if (dent->t_flag)
			wordok = 1;
		return;
	}

	if (strlen (w) < 3)
		return;

	p[0] = 0;
	p--;

	/* EST is now completely gone */

	if (p[0] == 'I' && !vowel (p[-1])) {
		p[0] = 'Y';
		if ((dent = lookup (w, strlen (w))) != NULL) {
			if (dent->t_flag)
				wordok = 1;
			return;
		}
	}

	if ((p[0] != 'E' && p[0] != 'Y') ||
	    (p[0] == 'Y' && vowel (p[-1]))) {
		if ((dent = lookup (w, strlen (w))) != NULL) {
			if (dent->t_flag)
				wordok = 1;
			return;
		}
	}

}


r_ending (w)
char *w;
{
	char *p;
	struct dent *dent;

	p = w + strlen (w) - 2;

	if (strcmp (p, "ER") != 0)
		return;

	p[1] = 0;	/* kill 'R' */

	if ((dent = lookup (w, strlen (w))) != NULL) {
		if (dent->r_flag)
			wordok = 1;
		return;
	}

	if (strlen (w) < 3)
		return;

	p[0] = 0;
	p--;

	/* ER is now completely gone */

	if (p[0] == 'I' && !vowel (p[-1])) {
		p[0] = 'Y';
		if ((dent = lookup (w, strlen (w))) != NULL) {
			if (dent->r_flag)
				wordok = 1;
			return;
		}
	}

	if ((p[0] != 'E' && p[0] != 'Y') ||
	    (p[0] == 'Y' && vowel (p[-1]))) {
		if ((dent = lookup (w, strlen (w))) != NULL) {
			if (dent->r_flag)
				wordok = 1;
			return;
		}
	}

}

h_ending (w)
char *w;
{
	char *p;
	struct dent *dent;

	p = w + strlen (w) - 2;

	if (strcmp (p, "TH") != 0)
		return;

	*p = 0;

	p -= 2;

	if (strcmp (p, "IE") == 0) {
		p[0] = 'Y';
		p[1] = 0;
	}

	if ((dent = lookup (w, strlen (w))) != NULL)
		if (dent->h_flag)
			wordok = 1;

}

/*
 * check for flags: X, J, Z, S, P, M
 *
 * X	-ions or -ications or -ens
 * J	-ings
 * Z	-ers or -iers
 * S	-ies or -es or -s
 * P	-iness or -ness
 * M	-'S
 */

s_ending (w)
char *w;
{
	char *p;
	struct dent *dent;

	p = w + strlen (w);

	p[-1] = 0;

	if (index ("SXZHY", p[-2]) == NULL || (p[-2] == 'Y'  && vowel (p[-3]))) {
		if ((dent = lookup (w, strlen (w))) != NULL) {
			if (dent->s_flag)
				wordok = 1;
			return;
		}
	}


	switch (p[-2]) {	/* letter before S */
	case 'N':	/* X */
		if (strcmp (p-4, "ION") == 0) {
			p[-4] = 'E';
			p[-3] = 0;
			if ((dent = lookup (w, strlen (w))) != NULL) {
				if (dent->x_flag)
					wordok = 1;
				return;
			}
		}
		if (strcmp (p-8, "ICATE") == 0) {
			p[-8] = 'Y';
			p[-7] = 0;
			if ((dent = lookup (w, strlen (w))) != NULL && dent->x_flag)
				wordok = 1;
			return;
		}
		if (strcmp (p-3, "EN") == 0 && p[-4] != 'E' && p[-4] != 'Y') {
			p[-3] = 0;
			if ((dent = lookup (w, strlen (w))) != NULL && dent->x_flag)
				wordok = 1;
			return;
		}
	case 'G':	/* J */
		if (strcmp (p-4, "ING") != 0)
			return;
		p[-4] = 'E';
		p[-3] = 0;
		if ((dent = lookup (w, strlen (w))) != NULL) {
			if (dent->j_flag)
				wordok = 1;
			return;
		}
		p[-4] = 0;
		if (p[-5] == 'E')
			return;
		if ((dent = lookup (w, strlen (w))) != NULL && dent->j_flag)
			wordok = 1;
		return;
	case 'R':	/* Z */
		if (strcmp (p-3, "ER") != 0)
			return;

		p[-2] = 0;
		if ((dent = lookup (w, strlen (w))) != NULL) {
			if (dent->z_flag)
				wordok = 1;
			return;
		}
		if (p[-4] == 'I') {
			p[-4] = 'Y';
			p[-3] = 0;
			if ((dent = lookup (w, strlen (w))) != NULL && dent->z_flag)
				wordok = 1;
			return;
		}
		p[-3] = 0;
		if ((dent = lookup (w, strlen (w))) != NULL && dent->z_flag)
			wordok = 1;
		return;
	case 'E': /* S (except simple adding of an S) */
		p[-2] = 0;	/* drop the ES */
		if ((dent = lookup (w, strlen (w))) != NULL) {
			if (dent->s_flag)
				wordok = 1;;
			return;
		}
		if (p[-3] == 'I') {
			p[-3] = 'Y';
			if ((dent = lookup (w, strlen (w))) != NULL && dent->s_flag)
				wordok = 1;
			return;
		}
		return;

	case 'S':	/* P */
		if (strcmp (p-4, "NES") != 0)
			return;

		p[-4] = 0;	/* kill 'N' */
		if (p[-5] != 'Y' || vowel (p[-6])) {
			if ((dent = lookup (w, strlen (w))) != NULL) {
				if (dent->p_flag)
					wordok = 1;
				return;
			}
		}
		if (p[-5] == 'I') {
			p[-5] = 'Y';
			if ((dent = lookup (w, strlen (w))) != NULL && dent->p_flag)
				wordok = 1;
			return;
		}
		return;
	case '\'':	/* M */
		wordok = 1;
		return;
	}
}

/* only the N flag */
n_ending (w)
char *w;
{
	char *p;
	struct dent *dent;

	p = w + strlen (w);

	if (p[-2] == 'E') {
		if (p[-3] == 'E' || p[-3] == 'Y')
			return;
		p[-2] = 0;
		if ((dent = lookup (w, strlen (w))) != NULL && dent->n_flag)
			wordok = 1;
		return;
	}

	if (strcmp (p-3, "ION") != 0)
		return;

	p[-3] = 'E';
	p[-2] = 0;

	if ((dent = lookup (w, strlen (w))) != NULL) {
		if (dent->n_flag)
			wordok = 1;
		return;
	}

	if (strcmp (p-7, "ICATE") != 0)	/* check is really against "ICATION" */
		return;

	p[-7] = 'Y';
	p[-6] = 0;
	
	if ((dent = lookup (w, strlen (w))) != NULL && dent->n_flag)
		wordok = 1;
	return;
}

/* flags: v */
e_ending (w)
char *w;
{
	char *p;
	struct dent *dent;

	p = w + strlen (w);

	if (strcmp (p-3, "IVE") != 0)
		return;
	p[-3] = 'E';
	p[-2] = 0;

	if ((dent = lookup (w, strlen (w))) != NULL) {
		if (dent->v_flag)
			wordok = 1;
		return;
	}

	if (p[-4] == 'E')
		return;

	p[-3] = 0;

	if ((dent = lookup (w, strlen (w))) != NULL && dent->v_flag)
		wordok = 1;
	return;
}

/* flags: y */
y_ending (w)
char *w;
{
	char *p;
	struct dent *dent;

	p = w + strlen (w);

	if (strcmp (p-2, "LY") != 0)
		return;

	p[-2] = 0;

	if ((dent = lookup (w, strlen (w))) != NULL && dent->y_flag)
		wordok = 1;
	return;
}

vowel (c)
char c;
{
	return (c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U');
}
SHAR_EOF
if test 8363 -ne "`wc -c < 'good.c'`"
then
	echo shar: "error transmitting 'good.c'" '(should have been 8363 characters)'
fi
fi
if test -f 'hash.c'
then
	echo shar: "will not over-write existing file 'hash.c'"
else
cat << \SHAR_EOF > 'hash.c'
/* -*- Mode:Text -*- */
/*
 * hash.c - a simple hash function for ispell
 *
 * Pace Willisson, 1983
 */

hash (s, n, hashsize)
register char *s;
register n;
register hashsize;
{
	register short h = 0;

	while (n--) {
		h ^= *s++;
		if (h < 0) {
			h <<= 1;
			h++;
		} else {
			h <<= 1;
		}
	}

	h &= 077777;
	return (h %= hashsize);
}



		
SHAR_EOF
if test 343 -ne "`wc -c < 'hash.c'`"
then
	echo shar: "error transmitting 'hash.c'" '(should have been 343 characters)'
fi
fi
if test -f 'ispell.c'
then
	echo shar: "will not over-write existing file 'ispell.c'"
else
cat << \SHAR_EOF > 'ispell.c'
/* -*- Mode:Text -*- */
/*
 * ispell.c - An interactive spelling corrector.
 *
 * Copyright (c), 1983, by Pace Willisson
 * Permission for non-profit use is hereby granted.
 * All other rights reserved.
 */

#include <stdio.h>
#include <ctype.h>
#include "ispell.h"

#define NOPARITY 0x7f 

FILE *infile;
FILE *outfile;

givehelp ()
{
	erase ();
	printf ("Whenever a word is found that is not in the dictionary,\r\n");
	printf ("it is printed on the first line of the screen.  If the dictionary\r\n");
	printf ("contains any similar words, they are listed with a single digit\r\n");
	printf ("next to each one.  You have the option of replacing the word\r\n");
	printf ("completely, or choosing one of the suggested words.\r\n");
	printf ("\r\n");
	printf ("Commands are:\r\n\r\n");
	printf ("R       Replace the misspelled word completely.\r\n");
	printf ("Space   Accept the word this time only\r\n");
	printf ("A       Accept the word for the rest of this file.\r\n");
	printf ("I       Accept the word, and put it in your private dictionary.\r\n");
	printf ("0-9     Replace with one of the suggested words.\r\n");
	printf ("Q       Write the rest of this file, ignoring misspellings, ");
	printf (         "and start next file.\r\n");
	printf ("X       Exit immediately.  Asks for conformation.  ");
	printf (         "Leaves file unchanged.\r\n");
	printf ("!       Shell escape.\r\n");
	printf ("^L      Redraw screen.\r\n");
	printf ("\r\n\r\n");
	printf ("-- Type space to continue --");
	fflush (stdout);
	getchar ();
}


char *getline();

int lflag = 0;
int aflag = 0;
int fflag = 0;
int sflag = 0;

char *askfilename;

usage ()
{
	fprintf (stderr, "Usage: spell [ file ... | -a | -l | -f file | -s ]\n");
	exit (1);
}

main (argc, argv)
char **argv;
{
	argv++;
	argc--;
	while (argc && **argv == '-') {
		switch ((*argv)[1]) {
		case 'a':
			aflag++;
			break;
		case 'f':
			fflag++;
			argv++; argc--;
			if (argc == 0)
				usage ();
			askfilename = *argv;
			break;
		case 'l':
			lflag++;
			break;
		case 's':
			sflag++;
			break;
		}
		argv++; argc--;
	}

	if (!argc && !lflag && !aflag)
		usage ();

	if (linit () < 0)
		exit (0);

	treeinit ();

	if (aflag) {
		askmode ();
		exit (0);
	}

	if (lflag) {
		infile = stdin;
		checkfile ();
		exit (0);
	}

	terminit ();

	while (argc--)
		dofile (*argv++);

	done ();
}

char firstbuf[BUFSIZ], secondbuf[BUFSIZ];
char *currentchar;
char token[BUFSIZ];

int quit;

char *currentfile = NULL;

dofile (filename)
char *filename;
{
	int c;
	char	bakfile[256];

	currentfile = filename;

	if ((infile = fopen (filename, "r")) == NULL) {
		fprintf (stderr, "Can't open %s\r\n", filename);
		sleep (2);
		return;
	}

	if (access (filename, 2) < 0) {
		fprintf (stderr, "Can't write to %s\r\n", filename);
		sleep (2);
		return;
	}

	strcpy (tempfile, "/usr/tmp/spellXXXXXX");
	mktemp (tempfile);
	if ((outfile = fopen (tempfile, "w")) == NULL) {
		fprintf (stderr, "Can't create %s\r\n", tempfile);
		sleep (2);
		return;
	}

	quit = 0;

	checkfile ();

	fclose (infile);
	fclose (outfile);

	treeoutput ();

	if ((infile = fopen (tempfile, "r")) == NULL) {
		fprintf (stderr, "tempoary file disappeared (%s)\r\n", tempfile);	
		sleep (2);
		return;
	}

	sprintf(bakfile, "%s.bak", filename);
	if(link(filename, bakfile) == 0)
		unlink(filename);

	if ((outfile = fopen (filename, "w")) == NULL) {
		fprintf (stderr, "can't create %s\r\n", filename);
		sleep (2);
		return;
	}

	while ((c = getc (infile)) != EOF)
		putc (c, outfile);

	fclose (infile);
	fclose (outfile);

	unlink (tempfile);
}

checkfile ()
{
	int c;
	char *p;
	int len;

	secondbuf[0] = 0;
	currentchar = secondbuf;

	while (1) {
		strcpy (firstbuf, secondbuf);
		if (quit) {	/* quit can't be set in l mode */
			while (fgets (secondbuf, sizeof secondbuf, infile) != NULL)
				fputs (secondbuf, outfile);
			break;
		}

		if (fgets (secondbuf, sizeof secondbuf, infile) == NULL)
			break;
		currentchar = secondbuf;
		
		len = strlen (secondbuf) - 1;
		if (secondbuf [ len ] == '\n')
			secondbuf [ len ] = 0;

		/* if this is a formatter command, skip over it */
		if (*currentchar == '.') {
			while (*currentchar && !isspace (*currentchar)) {
				if (!lflag)
					putc (*currentchar, outfile);
				currentchar++;
			}
			if (*currentchar == 0) {
				putc ('\n', outfile);
				continue;
			}
		}

		while (1) {
			while (*currentchar && !isalpha (*currentchar)) {
				/* formatting escape sequences */
				if (*currentchar == '\\') {
				    if(currentchar[1] == 'f') {
					/* font change: \fX */
					copyout(&currentchar, 3);
					continue;
				    }
				    else if(currentchar[1] == 's') {
					/* size change */
					if(currentchar[2] < 6 &&
					   currentchar[2] != 0)
						/* two digit size */
						copyout(&currentchar, 4);
					else
						/* one digit size */
						copyout(&currentchar, 3);
					continue;
				    }
				    else if(currentchar[1] == '(') {
					/* extended char set escape: \(XX */
					copyout(&currentchar, 4);
					continue;
				    }
				}

				if (!lflag)
					putc (*currentchar, outfile);
				currentchar++;
			}

			if (*currentchar == 0)
				break;

			p = token;
			while (isalpha (*currentchar) ||
			       (*currentchar == '\'' &&
				(isalpha (*(currentchar + 1)))))
			  *p++ = *currentchar++;
			*p = 0;
			if (lflag) {
				if (!good (token))
					printf ("%s\r\n", token);
			} else {
				if (!quit)
				correct (token, &currentchar);
			}
			if (!lflag)
				fprintf (outfile, "%s", token);
		}
		putc ('\n', outfile);
	}
}

char possibilities[10][BUFSIZ];
int pcount;

correct (token, currentchar)
char *token;
char **currentchar;
{
	int c;
	int i;
	char *p;
	int len;
	char *begintoken;

	len = strlen (token);
	begintoken = *currentchar - len;

checkagain:
	if (good (token))
		return;

	erase ();
	printf ("    %s", token);
	if (currentfile)
		printf ("              File: %s", currentfile);
	printf ("\r\n\r\n");

	makepossibilities (token);

	for (i = 0; i < 10; i++) {
		if (possibilities[i][0] == 0)
			break;
		printf ("%d: %s\r\n", i, possibilities[i]);
	}

	move (15, 0);
	printf ("%s\r\n", firstbuf);

	for (p = secondbuf; p != begintoken; p++)
		putchar (*p);
	inverse ();
	for (i = strlen (token); i > 0; i--)
		putchar (*p++);
	normal ();
	while (*p)
		putchar (*p++);
	printf ("\r\n");

	while (1) {
		switch (c = (getchar () & NOPARITY)) {
		case 'Z' & 037:
			stop ();
			erase ();
			goto checkagain;
		case ' ':
			erase ();
			return;
		case 'x': case 'X':
			printf ("Are you sure you want to throw away your changes? ");
			c = (getchar () & NOPARITY);
			if (c == 'y' || c == 'Y') {
				erase ();
				done ();
			}
			putchar (7);
			goto checkagain;
		case 'i': case 'I':
			treeinsert (token, 1);
			erase ();
			return;
		case 'a': case 'A':
			treeinsert (token, 0);
			erase ();
			return;
		case 'L' & 037:
			goto checkagain;
		case '?':
			givehelp ();
			goto checkagain;
		case '!':
			{
				char buf[200];
				move (18, 0);
				putchar ('!');
				if (getline (buf) == NULL) {
					putchar (7);
					erase ();
					goto checkagain;
				}
				printf ("\r\n");
				shellescape (buf);
				erase ();
				goto checkagain;
			}
		case 'r': case 'R':
			move (18, 0);
			printf ("Replace with: ");
			if (getline (token) == NULL) {
				putchar (7);
				erase ();
				goto checkagain;
			}
			inserttoken (secondbuf, begintoken, token, currentchar);
			erase ();
			goto checkagain;
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			if (possibilities[c - '0'][0] != 0) {
				strcpy (token, possibilities[c - '0']);
				inserttoken (secondbuf, begintoken, token, currentchar);				erase ();
				return;
			}
			putchar (7);
			break;
		case 'q': case 'Q':
			quit = 1;
			erase ();
			return;
		default:
			putchar (7);
			break;
		}
	}
}

inserttoken (buf, start, token, currentchar)
char *buf, *start, *token;
char **currentchar;
{
	char copy[BUFSIZ];
	char *p, *q;

	strcpy (copy, buf);

	for (p = buf, q = copy; p != start; p++, q++)
		*p = *q;
	while (*token)
		*p++ = *token++;
	q += *currentchar - start;
	*currentchar = p;
	while (*p++ = *q++)
		;
}


makepossibilities (word)
char word[];
{
	int i;

	for (i = 0; i < 10; i++)
		possibilities[i][0] = 0;
	pcount = 0;

	if (pcount < 10) wrongletter (word);
	if (pcount < 10) extraletter (word);
	if (pcount < 10) missingletter (word);
	if (pcount < 10) transposedletter (word);

}

char *cap();

insert (word)
char *word;
{
	int i;

	for (i = 0; i < pcount; i++)
		if (strcmp (possibilities[i], word) == 0)
			return (0);

	strcpy (possibilities[pcount++], word);
	if (pcount >= 10)
		return (-1);
	else
		return (0);
}

wrongletter (word)
char word[];
{
	int i, c, n;
	char newword[BUFSIZ];

	n = strlen (word);
	strcpy (newword, word);

	for (i = 0; i < n; i++) {
		for (newword[i] = 'A'; newword[i] <= 'Z'; newword[i]++) {
			if (good (newword)) {
				if (insert (cap (newword, word)) < 0)
					return;
			}
		}
		newword[i] = word[i];
	}
}

extraletter (word)
char word[];
{
	char newword[BUFSIZ], *p, *s, *t;

	if (strlen (word) < 3)
		return;

	for (p = word; *p; p++) {
		for (s = word, t = newword; *s; s++)
			if (s != p)
				*t++ = *s;
		*t = 0;
		if (good (newword)) {
			if (insert (cap (newword, word)) < 0)
				return;
		}
	}
}

missingletter (word)
char word[];
{
	char newword[BUFSIZ], *p, *r, *s, *t;

	for (p = word; p == word || p[-1]; p++) {
		for (s = newword, t = word; t != p; s++, t++)
			*s = *t;
		r = s++;
		while (*t)
			*s++ = *t++;
		*s = 0;
		for (*r = 'A'; *r <= 'Z'; (*r)++) {
			if (good (newword)) {
				if (insert (cap (newword, word)) < 0)
					return;
			}
		}
	}
}

transposedletter (word)
char word[];
{
	char newword[BUFSIZ];
	int t;
	char *p;

	strcpy (newword, word);
	for (p = newword; p[1]; p++) {
		t = p[0];
		p[0] = p[1];
		p[1] = t;
		if (good (newword)) {
			if (insert (cap (newword, word)) < 0)
				return;
		}
		t = p[0];
		p[0] = p[1];
		p[1] = t;
	}
}

char *
cap (word, pattern)
char word[], pattern[];
{
	static char newword[BUFSIZ];
	char *p, *q;

	if (*word == 0)
		return;

	if (isupper (pattern[0])) {
		if (isupper (pattern[1])) {
			for (p = word, q = newword; *p; p++, q++) {
				if (islower (*p))
					*q = toupper (*p);
				else
					*q = *p;
			}
			*q = 0;
		} else {
			if (islower (word [0]))
				newword[0] = toupper (word[0]);
			else
				newword[0] = word[0];

			for (p = word + 1, q = newword + 1; *p; p++, q++)
				if (isupper (*p))
					*q = tolower (*p);
				else
					*q = *p;

			*q = 0;
		}
	} else {
		for (p = word, q = newword; *p; p++, q++)
			if (isupper (*p))
				*q = tolower (*p);
			else
				*q = *p;
		*q = 0;
	}
	return (newword);
}

char *
getline (s)
char *s;
{
	char *p;
	int c;

	p = s;

	while (1) {
		c = (getchar () & NOPARITY);
		if (c == '\\') {
			putchar ('\\');
			c = (getchar () & NOPARITY);
			backup ();
			putchar (c);
			*p++ = c;
		} else if (c == ('G' & 037)) {
			return (NULL);
		} else if (c == '\n' || c == '\r') {
			*p = 0;
			return (s);
		} else if (c == erasechar) {
			if (p != s) {
				p--;
				backup ();
				putchar (' ');
				backup ();
			}
		} else if (c == killchar) {
			while (p != s) {
				p--;
				backup ();
				putchar (' ');
				backup ();
			}
		} else {
			*p++ = c;
			putchar (c);
		}
	}
}

askmode ()
{
	char buf[BUFSIZ];
	int i;

	if (fflag) {
		if (freopen (askfilename, "w", stdout) == NULL) {
			fprintf (stderr, "Can't create %s\n", askfilename);
			exit (1);
		}
	}

	setbuf (stdin, NULL);
	setbuf (stdout, NULL);

	while (gets (buf) != NULL) {
		if (good (buf)) {
			if (rootword[0] == 0) {
				printf ("*\n");	/* perfect match */
			} else {
				printf ("+ %s\n", rootword);
			}
		} else {
			makepossibilities (buf);
			if (possibilities[0][0]) {
				printf ("& ");
				for (i = 0; i < 10; i++) {
					if (possibilities[i][0] == 0)
						break;
					printf ("%s ", possibilities[i]);
				}
				printf ("\n");
			} else {
				printf ("#\n");
			}
		}
		if (sflag) {
			stop ();
			if (fflag) {
				rewind (stdout);
				creat (askfilename, 0666);
			}
		}
	}
}


copyout(cc, cnt)
char	**cc;
{
	while (--cnt >= 0) {
		if (*(*cc) == 0)
			break;
		if (!lflag)
			putc (*(*cc), outfile);
		(*cc)++;
	}

}
SHAR_EOF
if test 12171 -ne "`wc -c < 'ispell.c'`"
then
	echo shar: "error transmitting 'ispell.c'" '(should have been 12171 characters)'
fi
fi
if test -f 'ispell.el'
then
	echo shar: "will not over-write existing file 'ispell.el'"
else
cat << \SHAR_EOF > 'ispell.el'
;;; Spelling correction interface for GNU EMACS using "ispell"

;;; Walt Buehring
;;; Texas Instruments - Computer Science Center
;;; ARPA:  Buehring%TI-CSL@CSNet-Relay
;;; UUCP:  {smu, texsun, im4u, rice} ! ti-csl ! buehring

;;; Depends on the ispell program snarfed from MIT-PREP in early 
;;; 1986.  The only interactive command is "ispell-word" which should be
;;; bound to M-$.  If someone writes an "ispell-region" command, 
;;; I would appreciate a copy.

;;; To fully install this, add this file to your GNU lisp directory and 
;;; compile it with M-X byte-compile-file.  Then add the following to the
;;; appropriate init file:

;;;  (autoload 'ispell-word "ispell"
;;;    "Check the spelling of word in buffer." t)
;;;  (global-set-key "\e$" 'ispell-word)

;;; If run on a heavily loaded system, the timeout value in ispell-check 
;;; and the initial sleep time in ispell-init-process may need to be increased.

;;; No warranty expressed or implied.  All sales final.  Void where prohibited.
;;; If you don't like it, change it.

(defvar ispell-syntax-table nil)

(if (null ispell-syntax-table)
    ;; The following assumes that the standard-syntax-table
    ;; is static.  If you add words with funky characters
    ;; to your dictionary, the following may have to change.
    (progn
      (setq ispell-syntax-table (make-syntax-table))
      ;; Make certain characters word constituents
      (modify-syntax-entry ?' "w   " ispell-syntax-table)
      (modify-syntax-entry ?- "w   " ispell-syntax-table)
      ;; Get rid on existing word syntax on certain characters 
      (modify-syntax-entry ?$ ".   " ispell-syntax-table)
      (modify-syntax-entry ?% ".   " ispell-syntax-table)))


(defun ispell-word (&optional quietly)
  "Check spelling of word at or before dot.
If word not found in dictionary, display possible corrections in a window 
and let user select."
  (interactive)
  (let* ((current-syntax (syntax-table))
	 start end word poss replace)
    (unwind-protect
	(save-excursion
	  ;; Ensure syntax table is reasonable 
	  (set-syntax-table ispell-syntax-table)
	  ;; Move backward for word if not already on one.
	  (if (not (looking-at "\\w"))
	      (re-search-backward "\\w" (dot-min) 'stay))
	  ;; Move to start of word
	  (re-search-backward "\\W" (dot-min) 'stay)
	  ;; Find start and end of word
	  (or (re-search-forward "\\w+" nil t)
	      (error "No word to check."))
	  (setq start (match-beginning 0)
		end (match-end 0)
		word (buffer-substring start end)))
      (set-syntax-table current-syntax))
    (or quietly (message "Checking spelling of %s..." (upcase word)))
    (setq poss (ispell-check word))
    (cond ((eq poss t)
	   (or quietly (message "Found %s" (upcase word))))
	  ((stringp poss)
	   (or quietly (message "Found it because of %s" (upcase poss))))
	  ((null poss)
	   (or quietly (message "Could Not Find %s" (upcase word))))
	  (t (setq replace (ispell-choose poss))
	     (if replace
		 (progn
		   (goto-char end)
		   (delete-region start end)
		   (insert-string replace)))))
    poss))


(defun ispell-choose (choices)
  "Display possible corrections from list CHOICES.  Return chosen word or nil 
if none chosen."
  (unwind-protect 
      (save-window-excursion
	(let ((count 0)
	      (words choices)
	      (pick -1)
	      (window-min-height 2))
	  (overlay-window 3)
	  (switch-to-buffer "*Choices*") (erase-buffer)
	  (setq mode-line-format "--  %b  --")
	  (while words
	    (if (> (+ 7 (current-column) (length (car words))) (window-width))
		(insert "\n"))
	    (insert "(" (+ count ?a) ") " (car words) "  ")
	    (setq words (cdr words)
		  count (1+ count)))
	  (select-window (next-window))
	  (while (eq pick -1)
	    (message "Enter letter to replace word;  Space to flush")
	    (let* ((char (read-char))
		   (num (1+ (- (upcase char) ?A))))
	      (cond ((= char ? ) (setq pick 0))
		    ((or (<= num 0) (> num count)) (ding))
		    (t (setq pick num)))))
	  (and (> pick 0) (nth (1- pick) choices))))
    ;; Protected forms...
    (bury-buffer "*Choices*")))


(defun overlay-window (height)
  "Create a (usually small) window with HEIGHT lines and avoid
recentering."
  (save-excursion
    (let ((oldot (save-excursion (beginning-of-line) (dot)))
	  (top (save-excursion (move-to-window-line height) (dot)))
	  newin)
      (if (< oldot top) (setq top oldot))
      (setq newin (split-window-vertically height))
      (set-window-start newin top))))


(defvar ispell-process nil
  "Holds the process object for 'ispell'")

;;; create signal used by ispell-filter and ispell-check
(put 'ispell-output 'error-conditions '(ispell-output))

(defun ispell-check (word)
"Check spelling of string WORD, return either t for an exact match, a string
containing the root word for a match via suffix removal, a list of possible 
correct spellings, or nil for a complete miss."
  (ispell-init-process)
  (send-string ispell-process (concat word "\n"))
  (condition-case output
      (progn
	(sleep-for 20)
	(error "Timeout waiting for ispell process output"))
    (ispell-output (ispell-parse-output (car (cdr output))))))

(defun ispell-parse-output (output)
"Parse the OUTPUT string of 'ispell' and return a value as specified by the 
'ispell-check' function."
  (cond
   ((string= output "*") t)
   ((string= output "#") nil)
   ((string= (substring output 0 1) "+")
    (substring output 2))
   (t
    (let ((choice-list '()))
      (while (not (string= output ""))
	(let* ((start (string-match "[A-z]" output))
	       (end (string-match " \\|$" output start)))
	  (if start
	      (setq choice-list (cons (substring output start end)
				      choice-list)))
	  (setq output (substring output (1+ end)))))
      choice-list))))


(defvar ispell-process-output ""
  "Holds partial output from the 'ispell' process")

(defun ispell-filter (process output)
  "The filter-function for 'ispell'.  Signals complete line using the 
ispell-output signal"
  (if (string= "\n" (substring output (1- (length output))))
      (progn
	(setq output (concat ispell-process-output
			     (substring output 0 (1- (length output))))
	      ispell-process-output "")
	(signal 'ispell-output (list output)))
      (setq ispell-process-output (concat ispell-process-output output))))

(defun ispell-init-process ()
  "Check status of 'ispell' process and start if necessary; set up 
filter function for output."
  (if (or (not ispell-process)
	  (not (eq (process-status ispell-process) 'run)))
      (progn
	(message "Starting new ispell process...")
	(and (get-buffer "*ispell*") (kill-buffer "*ispell*"))
	(setq ispell-process (start-process "ispell" "*ispell*"
					   "ispell" "-a"))
	(set-process-filter ispell-process 'ispell-filter)
	(process-kill-without-query ispell-process)
	(sit-for 3))))

SHAR_EOF
if test 6763 -ne "`wc -c < 'ispell.el'`"
then
	echo shar: "error transmitting 'ispell.el'" '(should have been 6763 characters)'
fi
fi
if test -f 'ispell.h'
then
	echo shar: "will not over-write existing file 'ispell.h'"
else
cat << \SHAR_EOF > 'ispell.h'
/* -*- Mode: Text -*- */

#define LIBDIR "/usr/local/lib"

struct dent {
	struct dent *next;
	char *word;

	unsigned short used : 1;

/* bit fields for all of the flags */
	unsigned short v_flag : 1;
		/*
			"V" flag:
		        ...E --> ...IVE  as in CREATE --> CREATIVE
		        if # .ne. E, ...# --> ...#IVE  as in PREVENT --> PREVENTIVE
		*/
	unsigned short n_flag : 1;
		/*
			"N" flag:
			        ...E --> ...ION  as in CREATE --> CREATION
			        ...Y --> ...ICATION  as in MULTIPLY --> MULTIPLICATION
			        if # .ne. E or Y, ...# --> ...#EN  as in FALL --> FALLEN
		*/
	unsigned short x_flag : 1;
		/*
			"X" flag:
			        ...E --> ...IONS  as in CREATE --> CREATIONS
			        ...Y --> ...ICATIONS  as in MULTIPLY --> MULTIPLICATIONS
			        if # .ne. E or Y, ...# --> ...#ENS  as in WEAK --> WEAKENS
		*/
	unsigned short h_flag : 1;
		/*
			"H" flag:
			        ...Y --> ...IETH  as in TWENTY --> TWENTIETH
			        if # .ne. Y, ...# --> ...#TH  as in HUNDRED --> HUNDREDTH
		*/
	unsigned short y_flag : 1;
		/*
			"Y" FLAG:
			        ... --> ...LY  as in QUICK --> QUICKLY
		*/
	unsigned short g_flag : 1;
		/*
			"G" FLAG:
			        ...E --> ...ING  as in FILE --> FILING
			        if # .ne. E, ...# --> ...#ING  as in CROSS --> CROSSING
		*/
	unsigned short j_flag : 1;
		/*
			"J" FLAG"
			        ...E --> ...INGS  as in FILE --> FILINGS
			        if # .ne. E, ...# --> ...#INGS  as in CROSS --> CROSSINGS
		*/
	unsigned short d_flag : 1;
		/*
			"D" FLAG:
			        ...E --> ...ED  as in CREATE --> CREATED
			        if @ .ne. A, E, I, O, or U,
			                ...@Y --> ...@IED  as in IMPLY --> IMPLIED
			        if # .ne. E or Y, or (# = Y and @ = A, E, I, O, or U)
			                ...@# --> ...@#ED  as in CROSS --> CROSSED
			                                or CONVEY --> CONVEYED
		*/
	unsigned short t_flag : 1;
		/*
			"T" FLAG:
			        ...E --> ...EST  as in LATE --> LATEST
			        if @ .ne. A, E, I, O, or U,
			                ...@Y --> ...@IEST  as in DIRTY --> DIRTIEST
			        if # .ne. E or Y, or (# = Y and @ = A, E, I, O, or U)
			                ...@# --> ...@#EST  as in SMALL --> SMALLEST
			                                or GRAY --> GRAYEST
		*/
	unsigned short r_flag : 1;
		/*
			"R" FLAG:
			        ...E --> ...ER  as in SKATE --> SKATER
			        if @ .ne. A, E, I, O, or U,
			                ...@Y --> ...@IER  as in MULTIPLY --> MULTIPLIER
			        if # .ne. E or Y, or (# = Y and @ = A, E, I, O, or U)
			                ...@# --> ...@#ER  as in BUILD --> BUILDER
			                                or CONVEY --> CONVEYER
		*/
	unsigned short z_flag : 1;
		/*
			"Z FLAG:
			        ...E --> ...ERS  as in SKATE --> SKATERS
			        if @ .ne. A, E, I, O, or U,
			                ...@Y --> ...@IERS  as in MULTIPLY --> MULTIPLIERS
			        if # .ne. E or Y, or (# = Y and @ = A, E, I, O, or U)
			                ...@# --> ...@#ERS  as in BUILD --> BUILDERS
			                                or SLAY --> SLAYERS
		*/
	unsigned short s_flag : 1;
		/*
			"S" FLAG:
			        if @ .ne. A, E, I, O, or U,
			                ...@Y --> ...@IES  as in IMPLY --> IMPLIES
			        if # .eq. S, X, Z, or H,
			                ...# --> ...#ES  as in FIX --> FIXES
			        if # .ne. S,X,Z,H, or Y, or (# = Y and @ = A, E, I, O, or U)
			                ...# --> ...#S  as in BAT --> BATS
			                                or CONVEY --> CONVEYS
		*/
	unsigned short p_flag : 1;
		/*
			"P" FLAG:
			        if @ .ne. A, E, I, O, or U,
			                ...@Y --> ...@INESS  as in CLOUDY --> CLOUDINESS
			        if # .ne. Y, or @ = A, E, I, O, or U,
			                ...@# --> ...@#NESS  as in LATE --> LATENESS
			                                or GRAY --> GRAYNESS
		*/
	unsigned short m_flag : 1;
		/*
			"M" FLAG:
			        ... --> ...'S  as in DOG --> DOG'S
		*/

};

#define WORDLEN 30

struct hashheader {
	int magic;
	int stringsize;
	int tblsize;
};

#define MAGIC 1

	
/*
 * termcap variables
 */
char *tgetstr();
char PC;	/* padding character */
char *BC;	/* backspace if not ^H */
char *UP;	/* Upline (cursor up) */
char *cd;	/* clear to end of display */
char *ce;	/* clear to end of line */
char *cl;	/* clear display */
char *cm;	/* cursor movement */
char *dc;	/* delete character */
char *dl;	/* delete line */
char *dm;	/* delete mode */
char *ed;	/* exit delete mode */
char *ei;	/* exit insert mode */
char *ho;	/* home */
char *ic;	/* insert character */
char *il;	/* insert line */
char *im;	/* insert mode */
char *ip;	/* insert padding */
char *nd;	/* non-destructive space */
char *vb;	/* visible bell */
char *so;	/* standout */
char *se;	/* standout end */
int bs;
int li, co;	/* lines, columns */

char termcap[1024];
char termstr[1024];	/* for string values */
char *termptr;

char rootword[BUFSIZ];
struct dent *lastdent;

char *hashstrings;


int aflag;
int lflag;

struct node {
	struct node *left;
	struct node *right;
	char *word;
	int keep;
};

int erasechar;
int killchar;

char tempfile[200];
SHAR_EOF
if test 5025 -ne "`wc -c < 'ispell.h'`"
then
	echo shar: "error transmitting 'ispell.h'" '(should have been 5025 characters)'
fi
fi
if test -f 'lookup.c'
then
	echo shar: "will not over-write existing file 'lookup.c'"
else
cat << \SHAR_EOF > 'lookup.c'
/* -*- Mode:Text -*- */
/*
 * lookup.c - see if a word appears in the dictionary
 *
 * Pace Willisson, 1983
 */

#include <stdio.h>
#include <ctype.h>
#include "ispell.h"


struct dent *hashtbl;
int hashsize;

static inited = 0;

linit ()
{
	int hashfd;
	struct hashheader hashheader;
	char hashname[100];

	strcpy (hashname, LIBDIR);
	strcat (hashname, "/ispell.hash");

	if (inited)
		return;

	if ((hashfd = open ("ispell.hash", 0)) < 0 &&
	    (hashfd = open (hashname, 0)) < 0) {
		fprintf (stderr, "can't open %s\r\n", hashname);
		return (-1);
	}

	read (hashfd, &hashheader, sizeof hashheader);

	if (hashheader.magic != MAGIC) {
		fprintf (stderr, "Illegal format hash table\r\n");
		return (-1);
	}
	hashstrings = (char *) malloc (hashheader.stringsize);
	hashtbl = (struct dent *) malloc (hashheader.tblsize * sizeof (struct dent));
	hashsize = hashheader.tblsize;

	read (hashfd, hashstrings, hashheader.stringsize);
	read (hashfd, hashtbl, hashheader.tblsize * sizeof (struct dent));
	close (hashfd);

	inited = 1;
	return (0);
}

/* n is length of s */
struct dent *
lookup (s, n)
register char *s;
{
	register int i;
	register struct dent *dp;
	register char *s1, *s2;

	for (i = hash (s, n, hashsize); i > 0; i = (int)(dp->next)) {
		dp = &hashtbl[i];
		/* quick strcmp, but only for equality */
		s1 = &hashstrings [ (int)(dp->word) ];
		s2 = s;
		while (*s1 == *s2++)
			if (*s1++=='\0') {
				lastdent = &hashtbl[i];
				return (lastdent);
			}
	}
	return (NULL);
}

SHAR_EOF
if test 1486 -ne "`wc -c < 'lookup.c'`"
then
	echo shar: "error transmitting 'lookup.c'" '(should have been 1486 characters)'
fi
fi
if test -f 'term.c'
then
	echo shar: "will not over-write existing file 'term.c'"
else
cat << \SHAR_EOF > 'term.c'
/* -*- Mode:Text -*- */
/*
 * term.c - deal with termcap, and unix terminal mode settings
 *
 * Pace Willisson, 1983
 */

#include <stdio.h>
#include <sgtty.h>
#include <signal.h>
#include "ispell.h"

int putch();

erase ()
{
	if (cl)
		tputs(cl, li, putch);
	else {
		if (ho)
			tputs(ho, 100, putch);
		else if (cm)
			tputs(tgoto(cm, 0, 0), 100, putch);
		tputs(cd, li, putch);
	}
}

move (row, col)
{
	tputs (tgoto (cm, col, row), 100, putch);
}

inverse ()
{
	tputs (so, 10, putch);
}

normal ()
{
	tputs (se, 10, putch);
}

backup ()
{
	if (BC)
		tputs (BC, 1, putch);
	else
		putchar ('\b');
}

putch (c)
{
	putchar (c);
}

struct sgttyb sbuf, osbuf;
static termchanged = 0;


terminit ()
{
	int done();
	short tpgrp;
	int onstop();

retry:
	sigsetmask(1<<SIGTSTP | 1<<SIGTTIN | 1<<SIGTTOU);
	if (ioctl(0, TIOCGPGRP, &tpgrp) != 0) {
		fprintf (stderr, "Can't deal with non interactive use yet.\n");
		exit (1);
	}
	if (tpgrp != getpgrp(0)) { /* not in foreground */
		sigsetmask(1<<SIGTSTP | 1<<SIGTTIN);
		signal(SIGTTOU, SIG_DFL);
		kill(0, SIGTTOU);
		/* job stops here waiting for SIGCONT */
		goto retry;
	}

	ioctl (0, TIOCGETP, &osbuf);
	termchanged = 1;

	sbuf = osbuf;
	sbuf.sg_flags &= ~ECHO;
	sbuf.sg_flags |= RAW;
	ioctl (0, TIOCSETP, &sbuf);

	erasechar = sbuf.sg_erase;
	killchar = sbuf.sg_kill;

	signal (SIGINT, done);

	sigsetmask(0);
	signal(SIGTTIN, onstop);
	signal(SIGTTOU, onstop);
	signal(SIGTSTP, onstop);

	tgetent(termcap, getenv("TERM"));
	termptr = termstr;
	bs = tgetflag("bs");
	BC = tgetstr("bc", &termptr);
	UP = tgetstr("up", &termptr);
	cd = tgetstr("cd", &termptr);
	ce = tgetstr("ce", &termptr);	
	cl = tgetstr("cl", &termptr);
	cm = tgetstr("cm", &termptr);
	dc = tgetstr("dc", &termptr);
	dl = tgetstr("dl", &termptr);
	dm = tgetstr("dm", &termptr);
	ed = tgetstr("ed", &termptr);
	ei = tgetstr("ei", &termptr);
	ho = tgetstr("ho", &termptr);
	ic = tgetstr("ic", &termptr);
	il = tgetstr("al", &termptr);
	im = tgetstr("im", &termptr);
	ip = tgetstr("ip", &termptr);
	nd = tgetstr("nd", &termptr);
	vb = tgetstr("vb", &termptr);
	so = tgetstr("so", &termptr);	/* inverse video on */
	se = tgetstr("se", &termptr);	/* inverse video off */
	co = tgetnum("co");
	li = tgetnum("li");	

}

done ()
{
	unlink (tempfile);
	if (termchanged)
		ioctl (0, TIOCSETP, &osbuf);
	exit (0);
}

onstop(signo)
int signo;
{
	ioctl (0, TIOCSETP, &osbuf);
	signal(signo, SIG_DFL);
	kill(0, signo);
	/* stop here until continued */
	signal(signo, onstop);
	ioctl (0, TIOCSETP, &sbuf);
}

stop ()
{
	onstop (SIGTSTP);
}

shellescape (buf)
char *buf;
{
	ioctl (0, TIOCSETP, &osbuf);
	signal (SIGINT, 1);
	signal (SIGQUIT, 1);
	signal(SIGTTIN, SIG_DFL);
	signal(SIGTTOU, SIG_DFL);
	signal(SIGTSTP, SIG_DFL);

	system (buf);

	signal(SIGTTIN, onstop);
	signal(SIGTTOU, onstop);
	signal(SIGTSTP, onstop);
	signal (SIGINT, done);
	signal (SIGQUIT, SIG_DFL);

	ioctl (0, TIOCSETP, &sbuf);
	printf ("\n-- Type space to continue --");
	getchar ();
}
SHAR_EOF
if test 2964 -ne "`wc -c < 'term.c'`"
then
	echo shar: "error transmitting 'term.c'" '(should have been 2964 characters)'
fi
fi
if test -f 'tree.c'
then
	echo shar: "will not over-write existing file 'tree.c'"
else
cat << \SHAR_EOF > 'tree.c'
/* -*- Mode:Text -*- */
/*
 * tree.c - a tree style dictionary for user's personal words
 *
 * Pace Willisson, 1983
 */

#include <stdio.h>
#include <ctype.h>
#include "ispell.h"

char *getenv();
char *upcase();

static struct node *root = NULL;
struct node *tinsert();

static char personaldict[100];
static FILE *dictf;
static newwords = 0;

treeinit ()
{
	char *p;
	char buf[BUFSIZ];

	p = getenv ("HOME");
	if (p == NULL)
		return;

	strcpy (personaldict, p);
	strcat (personaldict, "/ispell.words");

	if ((dictf = fopen (personaldict, "r")) == NULL)
		return;

	while (fgets (buf, sizeof buf, dictf) != NULL) {
		int len = strlen (buf) - 1;

		if (buf [ len ] == '\n')
			buf [ len ] = 0;
		treeinsert (buf, 1);
	}

	fclose (dictf);

	newwords = 0;

	if (!lflag && !aflag && access (personaldict, 2) < 0)
		printf ("Warning: Cannot update personal dictionary (%s)\r\n", personaldict);
}

treeprint ()
{
	printf ("(");
	tprint (root);
	printf (")");
}

static
tprint (root)
struct node *root;
{
	if (root == NULL)
		return;
	printf ("%s ", root->word);
	tprint (root->left);
	tprint (root->right);
}


treeinsert (word, keep)
char *word;
{
	char nword[BUFSIZ];
	strcpy (nword, word);
	root = tinsert (upcase (nword), root, keep);
	newwords = 1;
}

static
struct node *
tinsert (word, root, keep)
char *word;
struct node *root;
{
	int cmp;

	if (root == NULL) {
		root = (struct node *) calloc (1, sizeof (struct node));
		root->word = (char *) malloc (strlen (word) + 1);
		strcpy (root->word, word);
		root->keep = keep;
		return (root);
	}

	cmp = strcmp (word, root->word);

	if (cmp == 0)
		return (root);

	if (cmp < 0)
		root->left = tinsert (word, root->left, keep);
	else
		root->right = tinsert (word, root->right, keep);

	return (root);
}

treelookup (word)
char *word;
{
	char nword[BUFSIZ];
	strcpy (nword, word);
	if (tlookup (upcase (nword), root)) {
		return (1);
	}
	return (0);
}

static
tlookup (word, root)
char *word;
struct node *root;
{
	int cmp;

	if (root == NULL)
		return (0);

	cmp = strcmp (word, root->word);

	if (cmp == 0)
		return (1);

	if (cmp < 0)
		return (tlookup (word, root->left));
	else
		return (tlookup (word, root->right));
}

treeoutput ()
{
	if (newwords == 0)
		return;

	if ((dictf = fopen (personaldict, "w")) == NULL) {
		fprintf (stderr, "Can't create %s\r\n", personaldict);
		return;
	}

	toutput1 (root);

	fclose (dictf);
}

static
toutput1 (root)
struct node *root;
{
	if (root == NULL)
		return;

	if (root->keep)
		fprintf (dictf, "%s\n", root->word);

	toutput1 (root->left);
	toutput1 (root->right);
}

char *
upcase (s)
register char *s;
{
	register char *os = s;

	while (*s) {
		if (islower (*s))
			*s = toupper (*s);
		s++;
	}
	return (os);
}
SHAR_EOF
if test 2714 -ne "`wc -c < 'tree.c'`"
then
	echo shar: "error transmitting 'tree.c'" '(should have been 2714 characters)'
fi
fi
exit 0
#	End of shell archive