xref: /netbsd-src/usr.bin/spell/spellprog/spellprog.c (revision d48f14661dda8638fee055ba15d35bdfb29b9fa8)
1 /*	$NetBSD: spellprog.c,v 1.4 2005/07/17 17:08:25 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 /*
134  * This cannot be const because we modify it when we choose british
135  * spelling.
136  */
137 static struct suftab {
138 	const char *suf;
139 	int (*p1)(char *, const char *, const char *, size_t);
140 	int n1;
141 	const char *d1;
142 	const char *a1;
143 	int (*p2)(char *, const char *, const char *, size_t);
144 	int n2;
145 	const char *d2;
146 	const char *a2;
147 } suftab[] = {
148 	{"ssen",     ily,    4, "-y+iness", "+ness" },
149 	{"ssel",     ily,    4, "-y+i+less", "+less" },
150 	{"se",       s,      1, "", "+s", es, 2, "-y+ies", "+es" },
151 	{"s'",       s,      2, "", "+'s"},
152 	{"s",        s,      1, "", "+s"},
153 	{"ecn",      ncy,    1, "", "-t+ce"},
154 	{"ycn",      ncy,    1, "", "-cy+t"},
155 	{"ytilb",    nop,    0, "", ""},
156 	{"ytilib",   bility, 5, "-le+ility", ""},
157 	{"elbaif",   i_to_y, 4, "-y+iable", ""},
158 	{"elba",     CCe,    4, "-e+able", "+able"},
159 	{"yti",      CCe,    3, "-e+ity", "+ity"},
160 	{"ylb",      y_to_e, 1, "-e+y", ""},
161 	{"yl",       ily,    2, "-y+ily", "+ly"},
162 	{"laci",     strip,  2, "", "+al"},
163 	{"latnem",   strip,  2, "", "+al"},
164 	{"lanoi",    strip,  2, "", "+al"},
165 	{"tnem",     strip,  4, "", "+ment"},
166 	{"gni",      CCe,    3, "-e+ing", "+ing"},
167 	{"reta",     nop,    0, "", ""},
168 	{"re",       strip,  1, "", "+r", i_to_y, 2, "-y+ier", "+er"},
169 	{"de",       strip,  1, "", "+d", i_to_y, 2, "-y+ied", "+ed"},
170 	{"citsi",    strip,  2, "", "+ic"},
171 	{"cihparg",  i_to_y, 1, "-y+ic", ""},
172 	{"tse",      strip,  2, "", "+st", i_to_y, 3, "-y+iest", "+est"},
173 	{"cirtem",   i_to_y, 1, "-y+ic", ""},
174 	{"yrtem",    metry,  0, "-ry+er", ""},
175 	{"cigol",    i_to_y, 1, "-y+ic", ""},
176 	{"tsigol",   i_to_y, 2, "-y+ist", ""},
177 	{"tsi",      VCe,    3, "-e+ist", "+ist"},
178 	{"msi",      VCe,    3, "-e+ism", "+ist"},
179 	{"noitacif", i_to_y, 6, "-y+ication", ""},
180 	{"noitazi",  ize,    5, "-e+ation", ""},
181 	{"rota",     tion,   2, "-e+or", ""},
182 	{"noit",     tion,   3, "-e+ion", "+ion"},
183 	{"naino",    an,     3, "", "+ian"},
184 	{"na",       an,     1, "", "+n"},
185 	{"evit",     tion,   3, "-e+ive", "+ive"},
186 	{"ezi",      CCe,    3, "-e+ize", "+ize"},
187 	{"pihs",     strip,  4, "", "+ship"},
188 	{"dooh",     ily,    4, "-y+hood", "+hood"},
189 	{"ekil",     strip,  4, "", "+like"},
190 	{ NULL, }
191 };
192 
193 static const char *preftab[] = {
194 	"anti",
195 	"bio",
196 	"dis",
197 	"electro",
198 	"en",
199 	"fore",
200 	"hyper",
201 	"intra",
202 	"inter",
203 	"iso",
204 	"kilo",
205 	"magneto",
206 	"meta",
207 	"micro",
208 	"milli",
209 	"mis",
210 	"mono",
211 	"multi",
212 	"non",
213 	"out",
214 	"over",
215 	"photo",
216 	"poly",
217 	"pre",
218 	"pseudo",
219 	"re",
220 	"semi",
221 	"stereo",
222 	"sub",
223 	"super",
224 	"thermo",
225 	"ultra",
226 	"under",	/* must precede un */
227 	"un",
228 	NULL
229 };
230 
231 static struct wlist {
232 	int fd;
233 	unsigned char *front;
234 	unsigned char *back;
235 } *wlists;
236 
237 static int vflag;
238 static int xflag;
239 static char word[LINE_MAX];
240 static char original[LINE_MAX];
241 static char affix[LINE_MAX];
242 static struct {
243 	const char **buf;
244 	size_t maxlev;
245 } deriv;
246 
247 /*
248  * The spellprog utility accepts a newline-delimited list of words
249  * on stdin.  For arguments it expects the path to a word list and
250  * the path to a file in which to store found words.
251  *
252  * In normal usage, spell is called twice.  The first time it is
253  * called with a stop list to flag commonly mispelled words.  The
254  * remaining words are then passed to spell again, this time with
255  * the dictionary file as the first (non-flag) argument.
256  *
257  * Unlike historic versions of spellprog, this one does not use
258  * hashed files.  Instead it simply requires that files be sorted
259  * lexigraphically and uses the same algorithm as the look utility.
260  *
261  * Note that spellprog should be called via the spell shell script
262  * and is not meant to be invoked directly by the user.
263  */
264 
265 int
266 main(int argc, char **argv)
267 {
268 	char *ep, *cp, *dp;
269 	char *outfile;
270 	int ch, fold, i;
271 	struct stat sb;
272 	FILE *file, *found;
273 
274 	setlocale(LC_ALL, "");
275 
276 	outfile = NULL;
277 	while ((ch = getopt(argc, argv, "bvxo:")) != -1) {
278 		switch (ch) {
279 		case 'b':
280 			/* Use British dictionary and convert ize -> ise. */
281 			ise();
282 			break;
283 		case 'o':
284 			outfile = optarg;
285 			break;
286 		case 'v':
287 			/* Also write derivations to "found" file. */
288 			vflag++;
289 			break;
290 		case 'x':
291 			/* Print plausible stems to stdout. */
292 			xflag++;
293 			break;
294 		default:
295 			usage();
296 		}
297 
298 	}
299 	argc -= optind;
300 	argv += optind;
301 	if (argc < 1)
302 		usage();
303 
304 	/* Open and mmap the word/stop lists. */
305 	if ((wlists = malloc(sizeof(struct wlist) * (argc + 1))) == NULL)
306 		err(1, "malloc");
307 
308 	for (i = 0; argc--; i++) {
309 		wlists[i].fd = open(argv[i], O_RDONLY, 0);
310 		if (wlists[i].fd == -1 || fstat(wlists[i].fd, &sb) != 0)
311 			err(1, "%s", argv[i]);
312 		if (sb.st_size > SIZE_T_MAX)
313 			errx(1, "%s: %s", argv[i], strerror(EFBIG));
314 		wlists[i].front = mmap(NULL, (size_t)sb.st_size, PROT_READ,
315 		    MAP_PRIVATE, wlists[i].fd, (off_t)0);
316 		if (wlists[i].front == MAP_FAILED)
317 			err(1, "%s", argv[i]);
318 		wlists[i].back = wlists[i].front + (size_t)sb.st_size;
319 	}
320 	wlists[i].fd = -1;
321 
322 	/* Open file where found words are to be saved. */
323 	if (outfile == NULL)
324 		found = NULL;
325 	else if ((found = fopen(outfile, "w")) == NULL)
326 		err(1, "cannot open %s", outfile);
327 
328 	for (;; print_word(file)) {
329 		affix[0] = '\0';
330 		file = found;
331 		for (ep = word; (*ep = ch = getchar()) != '\n'; ep++) {
332 			if (ep - word == sizeof(word) - 1) {
333 				*ep = '\0';
334 				warnx("word too long (%s)", word);
335 				while ((ch = getchar()) != '\n')
336 					;	/* slurp until EOL */
337 			}
338 			if (ch == EOF) {
339 				if (found != NULL)
340 					fclose(found);
341 				exit(0);
342 			}
343 		}
344 		for (cp = word, dp = original; cp < ep; )
345 			*dp++ = *cp++;
346 		*dp = '\0';
347 		fold = 0;
348 		for (cp = word; cp < ep; cp++)
349 			if (islower((unsigned char)*cp))
350 				goto lcase;
351 		if (trypref(ep, ".", 0))
352 			continue;
353 		++fold;
354 		for (cp = original + 1, dp = word + 1; dp < ep; dp++, cp++)
355 			*dp = tolower((unsigned char)*cp);
356 lcase:
357 		if (trypref(ep, ".", 0) || suffix(ep, 0))
358 			continue;
359 		if (isupper((unsigned char)word[0])) {
360 			for (cp = original, dp = word; (*dp = *cp++); dp++) {
361 				if (fold)
362 					*dp = tolower((unsigned char)*dp);
363 			}
364 			word[0] = tolower((unsigned char)word[0]);
365 			goto lcase;
366 		}
367 		file = stdout;
368 	}
369 }
370 
371 static void
372 print_word(FILE *f)
373 {
374 
375 	if (f != NULL) {
376 		if (vflag && affix[0] != '\0' && affix[0] != '.')
377 			fprintf(f, "%s\t%s\n", affix, original);
378 		else
379 			fprintf(f, "%s\n", original);
380 	}
381 }
382 
383 /*
384  * For each matching suffix in suftab, call the function associated
385  * with that suffix (p1 and p2).
386  */
387 static int
388 suffix(char *ep, size_t lev)
389 {
390 	const struct suftab *t;
391 	char *cp;
392 	const char *sp;
393 
394 	lev += DLEV;
395 	getderiv(lev + 1);
396 	deriv.buf[lev] = deriv.buf[lev - 1] = 0;
397 	for (t = suftab; (sp = t->suf) != NULL; t++) {
398 		cp = ep;
399 		while (*sp) {
400 			if (*--cp != *sp++)
401 				goto next;
402 		}
403 		for (sp = cp; --sp >= word && !vowel(*sp);)
404 			;	/* nothing */
405 		if (sp < word)
406 			return 0;
407 		if ((*t->p1)(ep - t->n1, t->d1, t->a1, lev + 1))
408 			return 1;
409 		if (t->p2 != NULL) {
410 			deriv.buf[lev] = deriv.buf[lev + 1] = '\0';
411 			return (*t->p2)(ep - t->n2, t->d2, t->a2, lev);
412 		}
413 		return 0;
414 next:		;
415 	}
416 	return 0;
417 }
418 
419 static int
420 /*ARGSUSED*/
421 nop(char *ep, const char *d, const char *a, size_t lev)
422 {
423 
424 	return 0;
425 }
426 
427 static int
428 /*ARGSUSED*/
429 strip(char *ep, const char *d, const char *a, size_t lev)
430 {
431 
432 	return trypref(ep, a, lev) || suffix(ep, lev);
433 }
434 
435 static int
436 s(char *ep, const char *d, const char *a, const size_t lev)
437 {
438 
439 	if (lev > DLEV + 1)
440 		return 0;
441 	if (*ep == 's' && ep[-1] == 's')
442 		return 0;
443 	return strip(ep, d, a, lev);
444 }
445 
446 static int
447 /*ARGSUSED*/
448 an(char *ep, const char *d, const char *a, size_t lev)
449 {
450 
451 	if (!isupper((unsigned char)*word))	/* must be proper name */
452 		return 0;
453 	return trypref(ep, a, lev);
454 }
455 
456 static int
457 /*ARGSUSED*/
458 ize(char *ep, const char *d, const char *a, size_t lev)
459 {
460 
461 	*ep++ = 'e';
462 	return strip(ep ,"", d, lev);
463 }
464 
465 static int
466 /*ARGSUSED*/
467 y_to_e(char *ep, const char *d, const char *a, size_t lev)
468 {
469 	char c = *ep;
470 
471 	*ep++ = 'e';
472 	if (strip(ep, "", d, lev))
473 		return 1;
474 	ep[-1] = c;
475 	return 0;
476 }
477 
478 static int
479 ily(char *ep, const char *d, const char *a, size_t lev)
480 {
481 
482 	if (ep[-1] == 'i')
483 		return i_to_y(ep, d, a, lev);
484 	else
485 		return strip(ep, d, a, lev);
486 }
487 
488 static int
489 ncy(char *ep, const char *d, const char *a, size_t lev)
490 {
491 
492 	if (skipv(skipv(ep - 1)) < word)
493 		return 0;
494 	ep[-1] = 't';
495 	return strip(ep, d, a, lev);
496 }
497 
498 static int
499 bility(char *ep, const char *d, const char *a, size_t lev)
500 {
501 
502 	*ep++ = 'l';
503 	return y_to_e(ep, d, a, lev);
504 }
505 
506 static int
507 i_to_y(char *ep, const char *d, const char *a, size_t lev)
508 {
509 
510 	if (ep[-1] == 'i') {
511 		ep[-1] = 'y';
512 		a = d;
513 	}
514 	return strip(ep, "", a, lev);
515 }
516 
517 static int
518 es(char *ep, const char *d, const char *a, size_t lev)
519 {
520 
521 	if (lev > DLEV)
522 		return 0;
523 
524 	switch (ep[-1]) {
525 	default:
526 		return 0;
527 	case 'i':
528 		return i_to_y(ep, d, a, lev);
529 	case 's':
530 	case 'h':
531 	case 'z':
532 	case 'x':
533 		return strip(ep, d, a, lev);
534 	}
535 }
536 
537 static int
538 metry(char *ep, const char *d, const char *a, size_t lev)
539 {
540 
541 	ep[-2] = 'e';
542 	ep[-1] = 'r';
543 	return strip(ep, d, a, lev);
544 }
545 
546 static int
547 tion(char *ep, const char *d, const char *a, size_t lev)
548 {
549 
550 	switch (ep[-2]) {
551 	case 'c':
552 	case 'r':
553 		return trypref(ep, a, lev);
554 	case 'a':
555 		return y_to_e(ep, d, a, lev);
556 	}
557 	return 0;
558 }
559 
560 /*
561  * Possible consonant-consonant-e ending.
562  */
563 static int
564 CCe(char *ep, const char *d, const char *a, size_t lev)
565 {
566 
567 	switch (ep[-1]) {
568 	case 'l':
569 		if (vowel(ep[-2]))
570 			break;
571 		switch (ep[-2]) {
572 		case 'l':
573 		case 'r':
574 		case 'w':
575 			break;
576 		default:
577 			return y_to_e(ep, d, a, lev);
578 		}
579 		break;
580 	case 's':
581 		if (ep[-2] == 's')
582 			break;
583 		/*FALLTHROUGH*/
584 	case 'c':
585 	case 'g':
586 		if (*ep == 'a')
587 			return 0;
588 		/*FALLTHROUGH*/
589 	case 'v':
590 	case 'z':
591 		if (vowel(ep[-2]))
592 			break;
593 		/*FALLTHROUGH*/
594 	case 'u':
595 		if (y_to_e(ep, d, a, lev))
596 			return 1;
597 		if (!(ep[-2] == 'n' && ep[-1] == 'g'))
598 			return 0;
599 	}
600 	return VCe(ep, d, a, lev);
601 }
602 
603 /*
604  * Possible consonant-vowel-consonant-e ending.
605  */
606 static int
607 VCe(char *ep, const char *d, const char *a, size_t lev)
608 {
609 	char c;
610 
611 	c = ep[-1];
612 	if (c == 'e')
613 		return 0;
614 	if (!vowel(c) && vowel(ep[-2])) {
615 		c = *ep;
616 		*ep++ = 'e';
617 		if (trypref(ep, d, lev) || suffix(ep, lev))
618 			return 1;
619 		ep--;
620 		*ep = c;
621 	}
622 	return strip(ep, d, a, lev);
623 }
624 
625 static const char *
626 lookuppref(char **wp, char *ep)
627 {
628 	const char **sp, *cp;
629 	char *bp;
630 
631 	for (sp = preftab; *sp; sp++) {
632 		bp = *wp;
633 		for (cp = *sp; *cp; cp++, bp++) {
634 			if (tolower((unsigned char)*bp) != *cp)
635 				goto next;
636 		}
637 		for (cp = bp; cp < ep; cp++) {
638 			if (vowel(*cp)) {
639 				*wp = bp;
640 				return *sp;
641 			}
642 		}
643 next:		;
644 	}
645 	return 0;
646 }
647 
648 /*
649  * If the word is not in the dictionary, try stripping off prefixes
650  * until the word is found or we run out of prefixes to check.
651  */
652 static int
653 trypref(char *ep, const char *a, size_t lev)
654 {
655 	const char *cp;
656 	char *bp;
657 	char *pp;
658 	int val = 0;
659 	char space[20];
660 
661 	getderiv(lev + 2);
662 	deriv.buf[lev] = a;
663 	if (tryword(word, ep, lev))
664 		return 1;
665 	bp = word;
666 	pp = space;
667 	deriv.buf[lev + 1] = pp;
668 	while ((cp = lookuppref(&bp, ep)) != NULL) {
669 		*pp++ = '+';
670 		while ((*pp = *cp++))
671 			pp++;
672 		if (tryword(bp, ep, lev + 1)) {
673 			val = 1;
674 			break;
675 		}
676 		if (pp - space >= sizeof(space))
677 			return 0;
678 	}
679 	deriv.buf[lev + 1] = deriv.buf[lev + 2] = '\0';
680 	return val;
681 }
682 
683 static int
684 tryword(char *bp, char *ep, size_t lev)
685 {
686 	size_t i, j;
687 	char duple[3];
688 
689 	if (ep-bp <= 1)
690 		return 0;
691 	if (vowel(*ep) && monosyl(bp, ep))
692 		return 0;
693 
694 	i = dict(bp, ep);
695 	if (i == 0 && vowel(*ep) && ep[-1] == ep[-2] &&
696 	    monosyl(bp, ep - 1)) {
697 		ep--;
698 		getderiv(++lev);
699 		deriv.buf[lev] = duple;
700 		duple[0] = '+';
701 		duple[1] = *ep;
702 		duple[2] = '\0';
703 		i = dict(bp, ep);
704 	}
705 	if (vflag == 0 || i == 0)
706 		return i;
707 
708 	/* Also tack on possible derivations. (XXX - warn on truncation?) */
709 	for (j = lev; j > 0; j--) {
710 		if (deriv.buf[j])
711 			(void)strlcat(affix, deriv.buf[j], sizeof(affix));
712 	}
713 	return i;
714 }
715 
716 static int
717 monosyl(char *bp, char *ep)
718 {
719 
720 	if (ep < bp + 2)
721 		return 0;
722 	if (vowel(*--ep) || !vowel(*--ep) || ep[1] == 'x' || ep[1] == 'w')
723 		return 0;
724 	while (--ep >= bp)
725 		if (vowel(*ep))
726 			return 0;
727 	return 1;
728 }
729 
730 static char *
731 skipv(char *st)
732 {
733 
734 	if (st >= word && vowel(*st))
735 		st--;
736 	while (st >= word && !vowel(*st))
737 		st--;
738 	return st;
739 }
740 
741 static int
742 vowel(int c)
743 {
744 
745 	switch (tolower(c)) {
746 	case 'a':
747 	case 'e':
748 	case 'i':
749 	case 'o':
750 	case 'u':
751 	case 'y':
752 		return 1;
753 	}
754 	return 0;
755 }
756 
757 /*
758  * Crummy way to Britishise.
759  */
760 static void
761 ise(void)
762 {
763 	struct suftab *tab;
764 	char *cp;
765 
766 	for (tab = suftab; tab->suf; tab++) {
767 		/* Assume that suffix will contain 'z' if a1 or d1 do */
768 		if (strchr(tab->suf, 'z')) {
769 			tab->suf = cp = estrdup(tab->suf);
770 			ztos(cp);
771 			if (strchr(tab->d1, 'z')) {
772 				tab->d1 = cp = estrdup(tab->d1);
773 				ztos(cp);
774 			}
775 			if (strchr(tab->a1, 'z')) {
776 				tab->a1 = cp = estrdup(tab->a1);
777 				ztos(cp);
778 			}
779 		}
780 	}
781 }
782 
783 static void
784 ztos(char *st)
785 {
786 
787 	for (; *st; st++)
788 		if (*st == 'z')
789 			*st = 's';
790 }
791 
792 static char *
793 estrdup(const char *st)
794 {
795 	char *d;
796 
797 	if ((d = strdup(st)) == NULL)
798 		err(1, "strdup");
799 	return d;
800 }
801 
802 /*
803  * Look up a word in the dictionary.
804  * Returns 1 if found, 0 if not.
805  */
806 static int
807 dict(char *bp, char *ep)
808 {
809 	char c;
810 	int i, rval;
811 
812 	c = *ep;
813 	*ep = '\0';
814 	if (xflag)
815 		printf("=%s\n", bp);
816 	for (i = rval = 0; wlists[i].fd != -1; i++) {
817 		if ((rval = look((unsigned char *)bp, wlists[i].front,
818 		    wlists[i].back)) == 1)
819 			break;
820 	}
821 	*ep = c;
822 	return rval;
823 }
824 
825 static void
826 getderiv(size_t lev)
827 {
828 	if (deriv.maxlev < lev) {
829 		void *p = realloc(deriv.buf, sizeof(*deriv.buf) * lev);
830 		if (p == NULL)
831 			err(1, "Cannot grow array");
832 		deriv.buf = p;
833 		deriv.maxlev = lev;
834 	}
835 }
836 
837 
838 static void
839 usage(void)
840 {
841 	(void)fprintf(stderr,
842 	    "Usage: %s [-bvx] [-o found-words] word-list ...\n",
843 	    getprogname());
844 	exit(1);
845 }
846