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