xref: /netbsd-src/usr.bin/spell/spellprog/spellprog.c (revision f9967d10c997b30f59931b55be2ff010c84270ef)
1 /*	$NetBSD: spellprog.c,v 1.2 2005/06/30 16:25:05 christos Exp $	*/
2 
3 /* derived from OpenBSD: spellprog.c,v 1.4 2003/06/03 02:56:16 millert Exp */
4 
5 /*
6  * Copyright (c) 1991, 1993
7  *	The Regents of the University of California.  All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  *	@(#)spell.h	8.1 (Berkeley) 6/6/93
34  */
35 /*
36  * Copyright (C) Caldera International Inc.  2001-2002.
37  * All rights reserved.
38  *
39  * Redistribution and use in source and binary forms, with or without
40  * modification, are permitted provided that the following conditions
41  * are met:
42  * 1. Redistributions of source code and documentation must retain the above
43  *    copyright notice, this list of conditions and the following disclaimer.
44  * 2. Redistributions in binary form must reproduce the above copyright
45  *    notice, this list of conditions and the following disclaimer in the
46  *    documentation and/or other materials provided with the distribution.
47  * 3. All advertising materials mentioning features or use of this software
48  *    must display the following acknowledgement:
49  *	This product includes software developed or owned by Caldera
50  *	International, Inc.
51  * 4. Neither the name of Caldera International, Inc. nor the names of other
52  *    contributors may be used to endorse or promote products derived from
53  *    this software without specific prior written permission.
54  *
55  * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
56  * INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
57  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
58  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
59  * IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE LIABLE FOR ANY DIRECT,
60  * INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
61  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
62  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
63  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
64  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
65  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
66  * POSSIBILITY OF SUCH DAMAGE.
67  */
68 
69 #ifndef lint
70 static const char copyright[] =
71 "@(#) Copyright (c) 1991, 1993\n\
72 	The Regents of the University of California.  All rights reserved.\n";
73 #endif /* not lint */
74 
75 #ifndef lint
76 #if 0
77 static const char sccsid[] = "@(#)spell.c	8.1 (Berkeley) 6/6/93";
78 #else
79 #endif
80 static const char rcsid[] = "$OpenBSD: spellprog.c,v 1.4 2003/06/03 02:56:16 millert Exp $";
81 #endif /* not lint */
82 
83 #include <sys/param.h>
84 #include <sys/mman.h>
85 #include <sys/stat.h>
86 
87 #include <ctype.h>
88 #include <err.h>
89 #include <errno.h>
90 #include <fcntl.h>
91 #include <limits.h>
92 #include <locale.h>
93 #include <stdio.h>
94 #include <stdlib.h>
95 #include <string.h>
96 #include <unistd.h>
97 
98 #include "extern.h"
99 
100 #define DLEV 2
101 
102 static int	 dict(char *, char *);
103 static int	 trypref(char *, const char *, size_t);
104 static int	 tryword(char *, char *, size_t);
105 static int	 suffix(char *, size_t);
106 static int	 vowel(int);
107 static const char *lookuppref(char **, char *);
108 static char	*skipv(char *);
109 static char	*estrdup(const char *);
110 static void	 ise(void);
111 static void	 print_word(FILE *);
112 static void	 ztos(char *);
113 static int	 monosyl(char *, char *);
114 static void 	 usage(void) __attribute__((__noreturn__));
115 static void	 getderiv(size_t);
116 
117 static int	 an(char *, const char *, const char *, size_t);
118 static int	 bility(char *, const char *, const char *, size_t);
119 static int	 es(char *, const char *, const char *, size_t);
120 static int	 i_to_y(char *, const char *, const char *, size_t);
121 static int	 ily(char *, const char *, const char *, size_t);
122 static int	 ize(char *, const char *, const char *, size_t);
123 static int	 metry(char *, const char *, const char *, size_t);
124 static int	 ncy(char *, const char *, const char *, size_t);
125 static int	 nop(char *, const char *, const char *, size_t);
126 static int	 s(char *, const char *, const char *, size_t);
127 static int	 strip(char *, const char *, const char *, size_t);
128 static int	 tion(char *, const char *, const char *, size_t);
129 static int	 y_to_e(char *, const char *, const char *, size_t);
130 static int	 CCe(char *, const char *, const char *, size_t);
131 static int	 VCe(char *, const char *, const char *, size_t);
132 
133 static struct suftab {
134 	const char *suf;
135 	int (*p1)(char *, const char *, const char *, size_t);
136 	int n1;
137 	const char *d1;
138 	const char *a1;
139 	int (*p2)(char *, const char *, const char *, size_t);
140 	int n2;
141 	const char *d2;
142 	const char *a2;
143 } suftab[] = {
144 	{"ssen",     ily,    4, "-y+iness", "+ness" },
145 	{"ssel",     ily,    4, "-y+i+less", "+less" },
146 	{"se",       s,      1, "", "+s", es, 2, "-y+ies", "+es" },
147 	{"s'",       s,      2, "", "+'s"},
148 	{"s",        s,      1, "", "+s"},
149 	{"ecn",      ncy,    1, "", "-t+ce"},
150 	{"ycn",      ncy,    1, "", "-cy+t"},
151 	{"ytilb",    nop,    0, "", ""},
152 	{"ytilib",   bility, 5, "-le+ility", ""},
153 	{"elbaif",   i_to_y, 4, "-y+iable", ""},
154 	{"elba",     CCe,    4, "-e+able", "+able"},
155 	{"yti",      CCe,    3, "-e+ity", "+ity"},
156 	{"ylb",      y_to_e, 1, "-e+y", ""},
157 	{"yl",       ily,    2, "-y+ily", "+ly"},
158 	{"laci",     strip,  2, "", "+al"},
159 	{"latnem",   strip,  2, "", "+al"},
160 	{"lanoi",    strip,  2, "", "+al"},
161 	{"tnem",     strip,  4, "", "+ment"},
162 	{"gni",      CCe,    3, "-e+ing", "+ing"},
163 	{"reta",     nop,    0, "", ""},
164 	{"re",       strip,  1, "", "+r", i_to_y, 2, "-y+ier", "+er"},
165 	{"de",       strip,  1, "", "+d", i_to_y, 2, "-y+ied", "+ed"},
166 	{"citsi",    strip,  2, "", "+ic"},
167 	{"cihparg",  i_to_y, 1, "-y+ic", ""},
168 	{"tse",      strip,  2, "", "+st", i_to_y, 3, "-y+iest", "+est"},
169 	{"cirtem",   i_to_y, 1, "-y+ic", ""},
170 	{"yrtem",    metry,  0, "-ry+er", ""},
171 	{"cigol",    i_to_y, 1, "-y+ic", ""},
172 	{"tsigol",   i_to_y, 2, "-y+ist", ""},
173 	{"tsi",      VCe,    3, "-e+ist", "+ist"},
174 	{"msi",      VCe,    3, "-e+ism", "+ist"},
175 	{"noitacif", i_to_y, 6, "-y+ication", ""},
176 	{"noitazi",  ize,    5, "-e+ation", ""},
177 	{"rota",     tion,   2, "-e+or", ""},
178 	{"noit",     tion,   3, "-e+ion", "+ion"},
179 	{"naino",    an,     3, "", "+ian"},
180 	{"na",       an,     1, "", "+n"},
181 	{"evit",     tion,   3, "-e+ive", "+ive"},
182 	{"ezi",      CCe,    3, "-e+ize", "+ize"},
183 	{"pihs",     strip,  4, "", "+ship"},
184 	{"dooh",     ily,    4, "-y+hood", "+hood"},
185 	{"ekil",     strip,  4, "", "+like"},
186 	{ NULL, }
187 };
188 
189 static const char *preftab[] = {
190 	"anti",
191 	"bio",
192 	"dis",
193 	"electro",
194 	"en",
195 	"fore",
196 	"hyper",
197 	"intra",
198 	"inter",
199 	"iso",
200 	"kilo",
201 	"magneto",
202 	"meta",
203 	"micro",
204 	"milli",
205 	"mis",
206 	"mono",
207 	"multi",
208 	"non",
209 	"out",
210 	"over",
211 	"photo",
212 	"poly",
213 	"pre",
214 	"pseudo",
215 	"re",
216 	"semi",
217 	"stereo",
218 	"sub",
219 	"super",
220 	"thermo",
221 	"ultra",
222 	"under",	/* must precede un */
223 	"un",
224 	NULL
225 };
226 
227 static struct wlist {
228 	int fd;
229 	unsigned char *front;
230 	unsigned char *back;
231 } *wlists;
232 
233 static int vflag;
234 static int xflag;
235 static char word[LINE_MAX];
236 static char original[LINE_MAX];
237 static char affix[LINE_MAX];
238 static struct {
239 	const char **buf;
240 	size_t maxlev;
241 } deriv;
242 
243 /*
244  * The spellprog utility accepts a newline-delimited list of words
245  * on stdin.  For arguments it expects the path to a word list and
246  * the path to a file in which to store found words.
247  *
248  * In normal usage, spell is called twice.  The first time it is
249  * called with a stop list to flag commonly mispelled words.  The
250  * remaining words are then passed to spell again, this time with
251  * the dictionary file as the first (non-flag) argument.
252  *
253  * Unlike historic versions of spellprog, this one does not use
254  * hashed files.  Instead it simply requires that files be sorted
255  * lexigraphically and uses the same algorithm as the look utility.
256  *
257  * Note that spellprog should be called via the spell shell script
258  * and is not meant to be invoked directly by the user.
259  */
260 
261 int
262 main(int argc, char **argv)
263 {
264 	char *ep, *cp, *dp;
265 	char *outfile;
266 	int ch, fold, i;
267 	struct stat sb;
268 	FILE *file, *found;
269 
270 	setlocale(LC_ALL, "");
271 
272 	outfile = NULL;
273 	while ((ch = getopt(argc, argv, "bvxo:")) != -1) {
274 		switch (ch) {
275 		case 'b':
276 			/* Use British dictionary and convert ize -> ise. */
277 			ise();
278 			break;
279 		case 'o':
280 			outfile = optarg;
281 			break;
282 		case 'v':
283 			/* Also write derivations to "found" file. */
284 			vflag++;
285 			break;
286 		case 'x':
287 			/* Print plausible stems to stdout. */
288 			xflag++;
289 			break;
290 		default:
291 			usage();
292 		}
293 
294 	}
295 	argc -= optind;
296 	argv += optind;
297 	if (argc < 1)
298 		usage();
299 
300 	/* Open and mmap the word/stop lists. */
301 	if ((wlists = malloc(sizeof(struct wlist) * (argc + 1))) == NULL)
302 		err(1, "malloc");
303 
304 	for (i = 0; argc--; i++) {
305 		wlists[i].fd = open(argv[i], O_RDONLY, 0);
306 		if (wlists[i].fd == -1 || fstat(wlists[i].fd, &sb) != 0)
307 			err(1, "%s", argv[i]);
308 		if (sb.st_size > SIZE_T_MAX)
309 			errx(1, "%s: %s", argv[i], strerror(EFBIG));
310 		wlists[i].front = mmap(NULL, (size_t)sb.st_size, PROT_READ,
311 		    MAP_PRIVATE, wlists[i].fd, (off_t)0);
312 		if (wlists[i].front == MAP_FAILED)
313 			err(1, "%s", argv[i]);
314 		wlists[i].back = wlists[i].front + (size_t)sb.st_size;
315 	}
316 	wlists[i].fd = -1;
317 
318 	/* Open file where found words are to be saved. */
319 	if (outfile == NULL)
320 		found = NULL;
321 	else if ((found = fopen(outfile, "w")) == NULL)
322 		err(1, "cannot open %s", outfile);
323 
324 	for (;; print_word(file)) {
325 		affix[0] = '\0';
326 		file = found;
327 		for (ep = word; (*ep = ch = getchar()) != '\n'; ep++) {
328 			if (ep - word == sizeof(word) - 1) {
329 				*ep = '\0';
330 				warnx("word too long (%s)", word);
331 				while ((ch = getchar()) != '\n')
332 					;	/* slurp until EOL */
333 			}
334 			if (ch == EOF) {
335 				if (found != NULL)
336 					fclose(found);
337 				exit(0);
338 			}
339 		}
340 		for (cp = word, dp = original; cp < ep; )
341 			*dp++ = *cp++;
342 		*dp = '\0';
343 		fold = 0;
344 		for (cp = word; cp < ep; cp++)
345 			if (islower((unsigned char)*cp))
346 				goto lcase;
347 		if (trypref(ep, ".", 0))
348 			continue;
349 		++fold;
350 		for (cp = original + 1, dp = word + 1; dp < ep; dp++, cp++)
351 			*dp = tolower((unsigned char)*cp);
352 lcase:
353 		if (trypref(ep, ".", 0) || suffix(ep, 0))
354 			continue;
355 		if (isupper((unsigned char)word[0])) {
356 			for (cp = original, dp = word; (*dp = *cp++); dp++) {
357 				if (fold)
358 					*dp = tolower((unsigned char)*dp);
359 			}
360 			word[0] = tolower((unsigned char)word[0]);
361 			goto lcase;
362 		}
363 		file = stdout;
364 	}
365 }
366 
367 static void
368 print_word(FILE *f)
369 {
370 
371 	if (f != NULL) {
372 		if (vflag && affix[0] != '\0' && affix[0] != '.')
373 			fprintf(f, "%s\t%s\n", affix, original);
374 		else
375 			fprintf(f, "%s\n", original);
376 	}
377 }
378 
379 /*
380  * For each matching suffix in suftab, call the function associated
381  * with that suffix (p1 and p2).
382  */
383 static int
384 suffix(char *ep, size_t lev)
385 {
386 	struct suftab *t;
387 	char *cp;
388 	const char *sp;
389 
390 	lev += DLEV;
391 	getderiv(lev + 1);
392 	deriv.buf[lev] = deriv.buf[lev - 1] = 0;
393 	for (t = suftab; (sp = t->suf) != NULL; t++) {
394 		cp = ep;
395 		while (*sp) {
396 			if (*--cp != *sp++)
397 				goto next;
398 		}
399 		for (sp = cp; --sp >= word && !vowel(*sp);)
400 			;	/* nothing */
401 		if (sp < word)
402 			return 0;
403 		if ((*t->p1)(ep - t->n1, t->d1, t->a1, lev + 1))
404 			return 1;
405 		if (t->p2 != NULL) {
406 			deriv.buf[lev] = deriv.buf[lev + 1] = '\0';
407 			return (*t->p2)(ep - t->n2, t->d2, t->a2, lev);
408 		}
409 		return 0;
410 next:		;
411 	}
412 	return 0;
413 }
414 
415 static int
416 /*ARGSUSED*/
417 nop(char *ep, const char *d, const char *a, size_t lev)
418 {
419 
420 	return 0;
421 }
422 
423 static int
424 /*ARGSUSED*/
425 strip(char *ep, const char *d, const char *a, size_t lev)
426 {
427 
428 	return trypref(ep, a, lev) || suffix(ep, lev);
429 }
430 
431 static int
432 s(char *ep, const char *d, const char *a, const size_t lev)
433 {
434 
435 	if (lev > DLEV + 1)
436 		return 0;
437 	if (*ep == 's' && ep[-1] == 's')
438 		return 0;
439 	return strip(ep, d, a, lev);
440 }
441 
442 static int
443 /*ARGSUSED*/
444 an(char *ep, const char *d, const char *a, size_t lev)
445 {
446 
447 	if (!isupper((unsigned char)*word))	/* must be proper name */
448 		return 0;
449 	return trypref(ep, a, lev);
450 }
451 
452 static int
453 /*ARGSUSED*/
454 ize(char *ep, const char *d, const char *a, size_t lev)
455 {
456 
457 	*ep++ = 'e';
458 	return strip(ep ,"", d, lev);
459 }
460 
461 static int
462 /*ARGSUSED*/
463 y_to_e(char *ep, const char *d, const char *a, size_t lev)
464 {
465 	char c = *ep;
466 
467 	*ep++ = 'e';
468 	if (strip(ep, "", d, lev))
469 		return 1;
470 	ep[-1] = c;
471 	return 0;
472 }
473 
474 static int
475 ily(char *ep, const char *d, const char *a, size_t lev)
476 {
477 
478 	if (ep[-1] == 'i')
479 		return i_to_y(ep, d, a, lev);
480 	else
481 		return strip(ep, d, a, lev);
482 }
483 
484 static int
485 ncy(char *ep, const char *d, const char *a, size_t lev)
486 {
487 
488 	if (skipv(skipv(ep - 1)) < word)
489 		return 0;
490 	ep[-1] = 't';
491 	return strip(ep, d, a, lev);
492 }
493 
494 static int
495 bility(char *ep, const char *d, const char *a, size_t lev)
496 {
497 
498 	*ep++ = 'l';
499 	return y_to_e(ep, d, a, lev);
500 }
501 
502 static int
503 i_to_y(char *ep, const char *d, const char *a, size_t lev)
504 {
505 
506 	if (ep[-1] == 'i') {
507 		ep[-1] = 'y';
508 		a = d;
509 	}
510 	return strip(ep, "", a, lev);
511 }
512 
513 static int
514 es(char *ep, const char *d, const char *a, size_t lev)
515 {
516 
517 	if (lev > DLEV)
518 		return 0;
519 
520 	switch (ep[-1]) {
521 	default:
522 		return 0;
523 	case 'i':
524 		return i_to_y(ep, d, a, lev);
525 	case 's':
526 	case 'h':
527 	case 'z':
528 	case 'x':
529 		return strip(ep, d, a, lev);
530 	}
531 }
532 
533 static int
534 metry(char *ep, const char *d, const char *a, size_t lev)
535 {
536 
537 	ep[-2] = 'e';
538 	ep[-1] = 'r';
539 	return strip(ep, d, a, lev);
540 }
541 
542 static int
543 tion(char *ep, const char *d, const char *a, size_t lev)
544 {
545 
546 	switch (ep[-2]) {
547 	case 'c':
548 	case 'r':
549 		return trypref(ep, a, lev);
550 	case 'a':
551 		return y_to_e(ep, d, a, lev);
552 	}
553 	return 0;
554 }
555 
556 /*
557  * Possible consonant-consonant-e ending.
558  */
559 static int
560 CCe(char *ep, const char *d, const char *a, size_t lev)
561 {
562 
563 	switch (ep[-1]) {
564 	case 'l':
565 		if (vowel(ep[-2]))
566 			break;
567 		switch (ep[-2]) {
568 		case 'l':
569 		case 'r':
570 		case 'w':
571 			break;
572 		default:
573 			return y_to_e(ep, d, a, lev);
574 		}
575 		break;
576 	case 's':
577 		if (ep[-2] == 's')
578 			break;
579 		/*FALLTHROUGH*/
580 	case 'c':
581 	case 'g':
582 		if (*ep == 'a')
583 			return 0;
584 		/*FALLTHROUGH*/
585 	case 'v':
586 	case 'z':
587 		if (vowel(ep[-2]))
588 			break;
589 		/*FALLTHROUGH*/
590 	case 'u':
591 		if (y_to_e(ep, d, a, lev))
592 			return 1;
593 		if (!(ep[-2] == 'n' && ep[-1] == 'g'))
594 			return 0;
595 	}
596 	return VCe(ep, d, a, lev);
597 }
598 
599 /*
600  * Possible consonant-vowel-consonant-e ending.
601  */
602 static int
603 VCe(char *ep, const char *d, const char *a, size_t lev)
604 {
605 	char c;
606 
607 	c = ep[-1];
608 	if (c == 'e')
609 		return 0;
610 	if (!vowel(c) && vowel(ep[-2])) {
611 		c = *ep;
612 		*ep++ = 'e';
613 		if (trypref(ep, d, lev) || suffix(ep, lev))
614 			return 1;
615 		ep--;
616 		*ep = c;
617 	}
618 	return strip(ep, d, a, lev);
619 }
620 
621 static const char *
622 lookuppref(char **wp, char *ep)
623 {
624 	const char **sp, *cp;
625 	char *bp;
626 
627 	for (sp = preftab; *sp; sp++) {
628 		bp = *wp;
629 		for (cp = *sp; *cp; cp++, bp++) {
630 			if (tolower((unsigned char)*bp) != *cp)
631 				goto next;
632 		}
633 		for (cp = bp; cp < ep; cp++) {
634 			if (vowel(*cp)) {
635 				*wp = bp;
636 				return *sp;
637 			}
638 		}
639 next:		;
640 	}
641 	return 0;
642 }
643 
644 /*
645  * If the word is not in the dictionary, try stripping off prefixes
646  * until the word is found or we run out of prefixes to check.
647  */
648 static int
649 trypref(char *ep, const char *a, size_t lev)
650 {
651 	const char *cp;
652 	char *bp;
653 	char *pp;
654 	int val = 0;
655 	char space[20];
656 
657 	getderiv(lev + 2);
658 	deriv.buf[lev] = a;
659 	if (tryword(word, ep, lev))
660 		return 1;
661 	bp = word;
662 	pp = space;
663 	deriv.buf[lev + 1] = pp;
664 	while ((cp = lookuppref(&bp, ep)) != NULL) {
665 		*pp++ = '+';
666 		while ((*pp = *cp++))
667 			pp++;
668 		if (tryword(bp, ep, lev + 1)) {
669 			val = 1;
670 			break;
671 		}
672 		if (pp - space >= sizeof(space))
673 			return 0;
674 	}
675 	deriv.buf[lev + 1] = deriv.buf[lev + 2] = '\0';
676 	return val;
677 }
678 
679 static int
680 tryword(char *bp, char *ep, size_t lev)
681 {
682 	size_t i, j;
683 	char duple[3];
684 
685 	if (ep-bp <= 1)
686 		return 0;
687 	if (vowel(*ep) && monosyl(bp, ep))
688 		return 0;
689 
690 	i = dict(bp, ep);
691 	if (i == 0 && vowel(*ep) && ep[-1] == ep[-2] &&
692 	    monosyl(bp, ep - 1)) {
693 		ep--;
694 		getderiv(++lev);
695 		deriv.buf[lev] = duple;
696 		duple[0] = '+';
697 		duple[1] = *ep;
698 		duple[2] = '\0';
699 		i = dict(bp, ep);
700 	}
701 	if (vflag == 0 || i == 0)
702 		return i;
703 
704 	/* Also tack on possible derivations. (XXX - warn on truncation?) */
705 	for (j = lev; j > 0; j--) {
706 		if (deriv.buf[j])
707 			(void)strlcat(affix, deriv.buf[j], sizeof(affix));
708 	}
709 	return i;
710 }
711 
712 static int
713 monosyl(char *bp, char *ep)
714 {
715 
716 	if (ep < bp + 2)
717 		return 0;
718 	if (vowel(*--ep) || !vowel(*--ep) || ep[1] == 'x' || ep[1] == 'w')
719 		return 0;
720 	while (--ep >= bp)
721 		if (vowel(*ep))
722 			return 0;
723 	return 1;
724 }
725 
726 static char *
727 skipv(char *st)
728 {
729 
730 	if (st >= word && vowel(*st))
731 		st--;
732 	while (st >= word && !vowel(*st))
733 		st--;
734 	return st;
735 }
736 
737 static int
738 vowel(int c)
739 {
740 
741 	switch (tolower(c)) {
742 	case 'a':
743 	case 'e':
744 	case 'i':
745 	case 'o':
746 	case 'u':
747 	case 'y':
748 		return 1;
749 	}
750 	return 0;
751 }
752 
753 /*
754  * Crummy way to Britishise.
755  */
756 static void
757 ise(void)
758 {
759 	struct suftab *tab;
760 	char *cp;
761 
762 	for (tab = suftab; tab->suf; tab++) {
763 		/* Assume that suffix will contain 'z' if a1 or d1 do */
764 		if (strchr(tab->suf, 'z')) {
765 			tab->suf = cp = estrdup(tab->suf);
766 			ztos(cp);
767 			if (strchr(tab->d1, 'z')) {
768 				tab->d1 = cp = estrdup(tab->d1);
769 				ztos(cp);
770 			}
771 			if (strchr(tab->a1, 'z')) {
772 				tab->a1 = cp = estrdup(tab->a1);
773 				ztos(cp);
774 			}
775 		}
776 	}
777 }
778 
779 static void
780 ztos(char *st)
781 {
782 
783 	for (; *st; st++)
784 		if (*st == 'z')
785 			*st = 's';
786 }
787 
788 static char *
789 estrdup(const char *st)
790 {
791 	char *d;
792 
793 	if ((d = strdup(st)) == NULL)
794 		err(1, "strdup");
795 	return d;
796 }
797 
798 /*
799  * Look up a word in the dictionary.
800  * Returns 1 if found, 0 if not.
801  */
802 static int
803 dict(char *bp, char *ep)
804 {
805 	char c;
806 	int i, rval;
807 
808 	c = *ep;
809 	*ep = '\0';
810 	if (xflag)
811 		printf("=%s\n", bp);
812 	for (i = rval = 0; wlists[i].fd != -1; i++) {
813 		if ((rval = look((unsigned char *)bp, wlists[i].front,
814 		    wlists[i].back)) == 1)
815 			break;
816 	}
817 	*ep = c;
818 	return rval;
819 }
820 
821 static void
822 getderiv(size_t lev)
823 {
824 	if (deriv.maxlev < lev) {
825 		void *p = realloc(deriv.buf, sizeof(*deriv.buf) * lev);
826 		if (p == NULL)
827 			err(1, "Cannot grow array");
828 		deriv.buf = p;
829 		deriv.maxlev = lev;
830 	}
831 }
832 
833 
834 static void
835 usage(void)
836 {
837 	(void)fprintf(stderr,
838 	    "Usage: %s [-bvx] [-o found-words] word-list ...\n",
839 	    getprogname());
840 	exit(1);
841 }
842