xref: /netbsd-src/usr.bin/units/units.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
1 /*	$NetBSD: units.c,v 1.25 2014/01/07 02:07:09 joerg Exp $	*/
2 
3 /*
4  * units.c   Copyright (c) 1993 by Adrian Mariano (adrian@cam.cornell.edu)
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. The name of the author may not be used to endorse or promote products
12  *    derived from this software without specific prior written permission.
13  * Disclaimer:  This software is provided by the author "as is".  The author
14  * shall not be liable for any damages caused in any way by this software.
15  *
16  * I would appreciate (though I do not require) receiving a copy of any
17  * improvements you might make to this program.
18  */
19 
20 #include <ctype.h>
21 #include <err.h>
22 #include <float.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27 
28 #include "pathnames.h"
29 
30 #define VERSION "1.0"
31 
32 #ifndef UNITSFILE
33 #define UNITSFILE _PATH_UNITSLIB
34 #endif
35 
36 #define MAXUNITS 1000
37 #define MAXPREFIXES 50
38 
39 #define MAXSUBUNITS 500
40 
41 #define PRIMITIVECHAR '!'
42 
43 static int precision = 8;		/* for printf with "%.*g" format */
44 
45 static const char *errprefix = NULL;	/* if not NULL, then prepend this
46 					 * to error messages and send them to
47 					 * stdout instead of stderr.
48 					 */
49 
50 static const char *powerstring = "^";
51 
52 static struct {
53 	const char *uname;
54 	const char *uval;
55 }      unittable[MAXUNITS];
56 
57 struct unittype {
58 	const char *numerator[MAXSUBUNITS];
59 	const char *denominator[MAXSUBUNITS];
60 	double factor;
61 };
62 
63 struct {
64 	const char *prefixname;
65 	const char *prefixval;
66 }      prefixtable[MAXPREFIXES];
67 
68 
69 static const char *NULLUNIT = "";
70 
71 static int unitcount;
72 static int prefixcount;
73 
74 
75 static int	addsubunit(const char *[], const char *);
76 static int	addunit(struct unittype *, const char *, int);
77 static void	cancelunit(struct unittype *);
78 static int	compare(const void *, const void *);
79 static int	compareproducts(const char **, const char **);
80 static int	compareunits(struct unittype *, struct unittype *);
81 static int	compareunitsreciprocal(struct unittype *, struct unittype *);
82 static int	completereduce(struct unittype *);
83 static void	initializeunit(struct unittype *);
84 static void	readerror(int);
85 static void	readunits(const char *);
86 static int	reduceproduct(struct unittype *, int);
87 static int	reduceunit(struct unittype *);
88 static void	showanswer(struct unittype *, struct unittype *);
89 static void	showunit(struct unittype *);
90 static void	sortunit(struct unittype *);
91 __dead static void	usage(void);
92 static void	zeroerror(void);
93 static char   *dupstr(const char *);
94 static const char *lookupunit(const char *);
95 
96 static char *
97 dupstr(const char *str)
98 {
99 	char *ret;
100 
101 	ret = strdup(str);
102 	if (!ret)
103 		err(3, "Memory allocation error");
104 	return (ret);
105 }
106 
107 
108 static __printflike(1, 2) void
109 mywarnx(const char *fmt, ...)
110 {
111 	va_list args;
112 
113 	va_start(args, fmt);
114 	if (errprefix) {
115 		/* warn to stdout, with errprefix prepended */
116 		printf("%s", errprefix);
117 		vprintf(fmt, args);
118 		printf("%s", "\n");
119 	} else {
120 		/* warn to stderr */
121 		vwarnx(fmt, args);
122 	}
123 	va_end(args);
124 }
125 
126 static void
127 readerror(int linenum)
128 {
129 	mywarnx("Error in units file '%s' line %d", UNITSFILE, linenum);
130 }
131 
132 
133 static void
134 readunits(const char *userfile)
135 {
136 	FILE *unitfile;
137 	char line[80], *lineptr;
138 	int len, linenum, i, isdup;
139 
140 	unitcount = 0;
141 	linenum = 0;
142 
143 	if (userfile) {
144 		unitfile = fopen(userfile, "rt");
145 		if (!unitfile)
146 			err(1, "Unable to open units file '%s'", userfile);
147 	}
148 	else {
149 		unitfile = fopen(UNITSFILE, "rt");
150 		if (!unitfile) {
151 			char *direc, *env;
152 			char filename[1000];
153 			char separator[2];
154 
155 			env = getenv("PATH");
156 			if (env) {
157 				if (strchr(env, ';'))
158 					strlcpy(separator, ";",
159 					    sizeof(separator));
160 				else
161 					strlcpy(separator, ":",
162 					    sizeof(separator));
163 				direc = strtok(env, separator);
164 				while (direc) {
165 					strlcpy(filename, "", sizeof(filename));
166 					strlcat(filename, direc,
167 					    sizeof(filename));
168 					strlcat(filename, "/",
169 					    sizeof(filename));
170 					strlcat(filename, UNITSFILE,
171 					    sizeof(filename));
172 					unitfile = fopen(filename, "rt");
173 					if (unitfile)
174 						break;
175 					direc = strtok(NULL, separator);
176 				}
177 			}
178 			if (!unitfile)
179 				errx(1, "Can't find units file '%s'",
180 				    UNITSFILE);
181 		}
182 	}
183 	while (!feof(unitfile)) {
184 		if (!fgets(line, 79, unitfile))
185 			break;
186 		linenum++;
187 		lineptr = line;
188 		if (*lineptr == '/')
189 			continue;
190 		lineptr += strspn(lineptr, " \n\t");
191 		len = strcspn(lineptr, " \n\t");
192 		lineptr[len] = 0;
193 		if (!strlen(lineptr))
194 			continue;
195 		if (lineptr[strlen(lineptr) - 1] == '-') { /* it's a prefix */
196 			if (prefixcount == MAXPREFIXES) {
197 				mywarnx(
198 			"Memory for prefixes exceeded in line %d",
199 					linenum);
200 				continue;
201 			}
202 			lineptr[strlen(lineptr) - 1] = 0;
203 			for (isdup = 0, i = 0; i < prefixcount; i++) {
204 				if (!strcmp(prefixtable[i].prefixname,
205 				    lineptr)) {
206 					isdup = 1;
207 					break;
208 				}
209 			}
210 			if (isdup) {
211 				mywarnx(
212 			"Redefinition of prefix '%s' on line %d ignored",
213 				    lineptr, linenum);
214 				continue;
215 			}
216 			prefixtable[prefixcount].prefixname = dupstr(lineptr);
217 			lineptr += len + 1;
218 			if (!strlen(lineptr)) {
219 				readerror(linenum);
220 				continue;
221 			}
222 			lineptr += strspn(lineptr, " \n\t");
223 			len = strcspn(lineptr, "\n\t");
224 			lineptr[len] = 0;
225 			prefixtable[prefixcount++].prefixval = dupstr(lineptr);
226 		}
227 		else {		/* it's not a prefix */
228 			if (unitcount == MAXUNITS) {
229 				mywarnx("Memory for units exceeded in line %d",
230 				    linenum);
231 				continue;
232 			}
233 			for (isdup = 0, i = 0; i < unitcount; i++) {
234 				if (!strcmp(unittable[i].uname, lineptr)) {
235 					isdup = 1;
236 					break;
237 				}
238 			}
239 			if (isdup) {
240 				mywarnx(
241 				"Redefinition of unit '%s' on line %d ignored",
242 				    lineptr, linenum);
243 				continue;
244 			}
245 			unittable[unitcount].uname = dupstr(lineptr);
246 			lineptr += len + 1;
247 			lineptr += strspn(lineptr, " \n\t");
248 			if (!strlen(lineptr)) {
249 				readerror(linenum);
250 				continue;
251 			}
252 			len = strcspn(lineptr, "\n\t");
253 			lineptr[len] = 0;
254 			unittable[unitcount++].uval = dupstr(lineptr);
255 		}
256 	}
257 	fclose(unitfile);
258 }
259 
260 static void
261 initializeunit(struct unittype * theunit)
262 {
263 	theunit->factor = 1.0;
264 	theunit->numerator[0] = theunit->denominator[0] = NULL;
265 }
266 
267 static int
268 addsubunit(const char *product[], const char *toadd)
269 {
270 	const char **ptr;
271 
272 	for (ptr = product; *ptr && *ptr != NULLUNIT; ptr++);
273 	if (ptr >= product + MAXSUBUNITS) {
274 		mywarnx("Memory overflow in unit reduction");
275 		return 1;
276 	}
277 	if (!*ptr)
278 		*(ptr + 1) = 0;
279 	*ptr = dupstr(toadd);
280 	return 0;
281 }
282 
283 static void
284 showunit(struct unittype * theunit)
285 {
286 	const char **ptr;
287 	int printedslash;
288 	int counter = 1;
289 
290 	printf("\t%.*g", precision, theunit->factor);
291 	for (ptr = theunit->numerator; *ptr; ptr++) {
292 		if (ptr > theunit->numerator && **ptr &&
293 		    !strcmp(*ptr, *(ptr - 1)))
294 			counter++;
295 		else {
296 			if (counter > 1)
297 				printf("%s%d", powerstring, counter);
298 			if (**ptr)
299 				printf(" %s", *ptr);
300 			counter = 1;
301 		}
302 	}
303 	if (counter > 1)
304 		printf("%s%d", powerstring, counter);
305 	counter = 1;
306 	printedslash = 0;
307 	for (ptr = theunit->denominator; *ptr; ptr++) {
308 		if (ptr > theunit->denominator && **ptr &&
309 		    !strcmp(*ptr, *(ptr - 1)))
310 			counter++;
311 		else {
312 			if (counter > 1)
313 				printf("%s%d", powerstring, counter);
314 			if (**ptr) {
315 				if (!printedslash)
316 					printf(" /");
317 				printedslash = 1;
318 				printf(" %s", *ptr);
319 			}
320 			counter = 1;
321 		}
322 	}
323 	if (counter > 1)
324 		printf("%s%d", powerstring, counter);
325 	printf("\n");
326 }
327 
328 static void
329 zeroerror(void)
330 {
331 	mywarnx("Unit reduces to zero");
332 }
333 
334 /*
335    Adds the specified string to the unit.
336    Flip is 0 for adding normally, 1 for adding reciprocal.
337 
338    Returns 0 for successful addition, nonzero on error.
339 */
340 
341 static int
342 addunit(struct unittype * theunit, const char *toadd, int flip)
343 {
344 	char *scratch, *savescr;
345 	char *item;
346 	char *divider, *slash;
347 	int doingtop;
348 
349 	savescr = scratch = dupstr(toadd);
350 	for (slash = scratch + 1; *slash; slash++)
351 		if (*slash == '-' &&
352 		    (tolower((unsigned char)*(slash - 1)) != 'e' ||
353 		    !strchr(".0123456789", *(slash + 1))))
354 			*slash = ' ';
355 	slash = strchr(scratch, '/');
356 	if (slash)
357 		*slash = 0;
358 	doingtop = 1;
359 	do {
360 		item = strtok(scratch, " *\t\n/");
361 		while (item) {
362 			if (strchr("0123456789.", *item)) {
363 				/* item starts with a number */
364 				char *endptr;
365 				double num;
366 
367 				divider = strchr(item, '|');
368 				if (divider) {
369 					*divider = 0;
370 					num = strtod(item, &endptr);
371 					if (!num) {
372 						zeroerror();
373 						return 1;
374 					}
375 					if (endptr != divider) {
376 						/* "6foo|2" is an error */
377 						mywarnx("Junk before '|'");
378 						return 1;
379 					}
380 					if (doingtop ^ flip)
381 						theunit->factor *= num;
382 					else
383 						theunit->factor /= num;
384 					num = strtod(divider + 1, &endptr);
385 					if (!num) {
386 						zeroerror();
387 						return 1;
388 					}
389 					if (doingtop ^ flip)
390 						theunit->factor /= num;
391 					else
392 						theunit->factor *= num;
393 					if (*endptr) {
394 						/* "6|2foo" is like "6|2 foo" */
395 						item = endptr;
396 						continue;
397 					}
398 				}
399 				else {
400 					num = strtod(item, &endptr);
401 					if (!num) {
402 						zeroerror();
403 						return 1;
404 					}
405 					if (doingtop ^ flip)
406 						theunit->factor *= num;
407 					else
408 						theunit->factor /= num;
409 					if (*endptr) {
410 						/* "3foo" is like "3 foo" */
411 						item = endptr;
412 						continue;
413 					}
414 				}
415 			}
416 			else {	/* item is not a number */
417 				int repeat = 1;
418 
419 				if (strchr("23456789",
420 				    item[strlen(item) - 1])) {
421 					repeat = item[strlen(item) - 1] - '0';
422 					item[strlen(item) - 1] = 0;
423 				}
424 				for (; repeat; repeat--)
425 					if (addsubunit(doingtop ^ flip ? theunit->numerator : theunit->denominator, item))
426 						return 1;
427 			}
428 			item = strtok(NULL, " *\t/\n");
429 		}
430 		doingtop--;
431 		if (slash) {
432 			scratch = slash + 1;
433 		}
434 		else
435 			doingtop--;
436 	} while (doingtop >= 0);
437 	free(savescr);
438 	return 0;
439 }
440 
441 static int
442 compare(const void *item1, const void *item2)
443 {
444 	return strcmp(*(const char * const *) item1,
445 		      *(const char * const *) item2);
446 }
447 
448 static void
449 sortunit(struct unittype * theunit)
450 {
451 	const char **ptr;
452 	int count;
453 
454 	for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++);
455 	qsort(theunit->numerator, count, sizeof(char *), compare);
456 	for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++);
457 	qsort(theunit->denominator, count, sizeof(char *), compare);
458 }
459 
460 static void
461 cancelunit(struct unittype * theunit)
462 {
463 	const char **den, **num;
464 	int comp;
465 
466 	den = theunit->denominator;
467 	num = theunit->numerator;
468 
469 	while (*num && *den) {
470 		comp = strcmp(*den, *num);
471 		if (!comp) {
472 /*      if (*den!=NULLUNIT) free(*den);
473       if (*num!=NULLUNIT) free(*num);*/
474 			*den++ = NULLUNIT;
475 			*num++ = NULLUNIT;
476 		}
477 		else if (comp < 0)
478 			den++;
479 		else
480 			num++;
481 	}
482 }
483 
484 
485 
486 
487 /*
488    Looks up the definition for the specified unit.
489    Returns a pointer to the definition or a null pointer
490    if the specified unit does not appear in the units table.
491 */
492 
493 static char buffer[100];	/* buffer for lookupunit answers with
494 				   prefixes */
495 
496 static const char *
497 lookupunit(const char *unit)
498 {
499 	int i;
500 	char *copy;
501 
502 	for (i = 0; i < unitcount; i++) {
503 		if (!strcmp(unittable[i].uname, unit))
504 			return unittable[i].uval;
505 	}
506 
507 	if (unit[strlen(unit) - 1] == '^') {
508 		copy = dupstr(unit);
509 		copy[strlen(copy) - 1] = 0;
510 		for (i = 0; i < unitcount; i++) {
511 			if (!strcmp(unittable[i].uname, copy)) {
512 				strlcpy(buffer, copy, sizeof(buffer));
513 				free(copy);
514 				return buffer;
515 			}
516 		}
517 		free(copy);
518 	}
519 	if (unit[strlen(unit) - 1] == 's') {
520 		copy = dupstr(unit);
521 		copy[strlen(copy) - 1] = 0;
522 		for (i = 0; i < unitcount; i++) {
523 			if (!strcmp(unittable[i].uname, copy)) {
524 				strlcpy(buffer, copy, sizeof(buffer));
525 				free(copy);
526 				return buffer;
527 			}
528 		}
529 		if (copy[strlen(copy) - 1] == 'e') {
530 			copy[strlen(copy) - 1] = 0;
531 			for (i = 0; i < unitcount; i++) {
532 				if (!strcmp(unittable[i].uname, copy)) {
533 					strlcpy(buffer, copy, sizeof(buffer));
534 					free(copy);
535 					return buffer;
536 				}
537 			}
538 		}
539 		free(copy);
540 	}
541 	for (i = 0; i < prefixcount; i++) {
542 		if (!strncmp(prefixtable[i].prefixname, unit,
543 			strlen(prefixtable[i].prefixname))) {
544 			unit += strlen(prefixtable[i].prefixname);
545 			if (!strlen(unit) || lookupunit(unit)) {
546 				strlcpy(buffer, prefixtable[i].prefixval,
547 				    sizeof(buffer));
548 				strlcat(buffer, " ", sizeof(buffer));
549 				strlcat(buffer, unit, sizeof(buffer));
550 				return buffer;
551 			}
552 		}
553 	}
554 	return 0;
555 }
556 
557 
558 
559 /*
560    reduces a product of symbolic units to primitive units.
561    The three low bits are used to return flags:
562 
563      bit 0 (1) set on if reductions were performed without error.
564      bit 1 (2) set on if no reductions are performed.
565      bit 2 (4) set on if an unknown unit is discovered.
566 */
567 
568 
569 #define ERROR 4
570 
571 static int
572 reduceproduct(struct unittype * theunit, int flip)
573 {
574 
575 	const char *toadd;
576 	const char **product;
577 	int didsomething = 2;
578 
579 	if (flip)
580 		product = theunit->denominator;
581 	else
582 		product = theunit->numerator;
583 
584 	for (; *product; product++) {
585 
586 		for (;;) {
587 			if (!strlen(*product))
588 				break;
589 			toadd = lookupunit(*product);
590 			if (!toadd) {
591 				mywarnx("Unknown unit '%s'", *product);
592 				return ERROR;
593 			}
594 			if (strchr(toadd, PRIMITIVECHAR))
595 				break;
596 			didsomething = 1;
597 			if (*product != NULLUNIT) {
598 				free(__UNCONST(*product));
599 				*product = NULLUNIT;
600 			}
601 			if (addunit(theunit, toadd, flip))
602 				return ERROR;
603 		}
604 	}
605 	return didsomething;
606 }
607 
608 
609 /*
610    Reduces numerator and denominator of the specified unit.
611    Returns 0 on success, or 1 on unknown unit error.
612 */
613 
614 static int
615 reduceunit(struct unittype * theunit)
616 {
617 	int ret;
618 
619 	ret = 1;
620 	while (ret & 1) {
621 		ret = reduceproduct(theunit, 0) | reduceproduct(theunit, 1);
622 		if (ret & 4)
623 			return 1;
624 	}
625 	return 0;
626 }
627 
628 static int
629 compareproducts(const char **one, const char **two)
630 {
631 	while (*one || *two) {
632 		if (!*one && *two != NULLUNIT)
633 			return 1;
634 		if (!*two && *one != NULLUNIT)
635 			return 1;
636 		if (*one == NULLUNIT)
637 			one++;
638 		else if (*two == NULLUNIT)
639 			two++;
640 		else if (*one && *two && strcmp(*one, *two))
641 			return 1;
642 		else
643 			one++, two++;
644 	}
645 	return 0;
646 }
647 
648 
649 /* Return zero if units are compatible, nonzero otherwise */
650 
651 static int
652 compareunits(struct unittype * first, struct unittype * second)
653 {
654 	return
655 	compareproducts(first->numerator, second->numerator) ||
656 	compareproducts(first->denominator, second->denominator);
657 }
658 
659 static int
660 compareunitsreciprocal(struct unittype * first, struct unittype * second)
661 {
662 	return
663 	compareproducts(first->numerator, second->denominator) ||
664 	compareproducts(first->denominator, second->numerator);
665 }
666 
667 
668 static int
669 completereduce(struct unittype * unit)
670 {
671 	if (reduceunit(unit))
672 		return 1;
673 	sortunit(unit);
674 	cancelunit(unit);
675 	return 0;
676 }
677 
678 
679 static void
680 showanswer(struct unittype * have, struct unittype * want)
681 {
682 	if (compareunits(have, want)) {
683 		if (compareunitsreciprocal(have, want)) {
684 			printf("conformability error\n");
685 			showunit(have);
686 			showunit(want);
687 		} else {
688 			printf("\treciprocal conversion\n");
689 			printf("\t* %.*g\n\t/ %.*g\n",
690 			    precision, 1 / (have->factor * want->factor),
691 			    precision, want->factor * have->factor);
692 		}
693 	}
694 	else
695 		printf("\t* %.*g\n\t/ %.*g\n",
696 		    precision, have->factor / want->factor,
697 		    precision, want->factor / have->factor);
698 }
699 
700 static int
701 listunits(int expand)
702 {
703 	struct unittype theunit;
704 	const char *thename;
705 	const char *thedefn;
706 	int errors = 0;
707 	int i;
708 	int printexpansion;
709 
710 	/*
711 	 * send error and warning messages to stdout,
712 	 * and make them look like comments.
713 	 */
714 	errprefix = "/ ";
715 
716 #if 0 /* debug */
717 	printf("/ expand=%d precision=%d unitcount=%d prefixcount=%d\n",
718 	    expand, precision, unitcount, prefixcount);
719 #endif
720 
721 	/* 1. Dump all primitive units, e.g. "m !a!", "kg !b!", ... */
722 	printf("/ Primitive units\n");
723 	for (i = 0; i < unitcount; i++) {
724 		thename = unittable[i].uname;
725 		thedefn = unittable[i].uval;
726 		if (thedefn[0] == PRIMITIVECHAR) {
727 			printf("%s\t%s\n", thename, thedefn);
728 		}
729 	}
730 
731 	/* 2. Dump all prefixes, e.g. "yotta- 1e24", "zetta- 1e21", ... */
732 	printf("/ Prefixes\n");
733 	for (i = 0; i < prefixcount; i++) {
734 		printexpansion = expand;
735 		thename = prefixtable[i].prefixname;
736 		thedefn = prefixtable[i].prefixval;
737 		if (expand) {
738 			/*
739 			 * prefix names are sometimes identical to unit
740 			 * names, so we have to expand thedefn instead of
741 			 * expanding thename.
742 			 */
743 			initializeunit(&theunit);
744 			if (addunit(&theunit, thedefn, 0) != 0
745 			    || completereduce(&theunit) != 0) {
746 				errors++;
747 				printexpansion = 0;
748 				mywarnx("Error in prefix '%s-'", thename);
749 			}
750 		}
751 		if (printexpansion) {
752 			printf("%s-", thename);
753 			showunit(&theunit);
754 		} else
755 			printf("%s-\t%s\n", thename, thedefn);
756 	}
757 
758 	/* 3. Dump all other units. */
759 	printf("/ Other units\n");
760 	for (i = 0; i < unitcount; i++) {
761 		printexpansion = expand;
762 		thename = unittable[i].uname;
763 		thedefn = unittable[i].uval;
764 		if (thedefn[0] == PRIMITIVECHAR)
765 			continue;
766 		if (expand) {
767 			/*
768 			 * expand thename, not thedefn, so that
769 			 * we can catch errors in the name itself.
770 			 * e.g. a name that contains a hyphen
771 			 * will be interpreted as multiplication.
772 			 */
773 			initializeunit(&theunit);
774 			if (addunit(&theunit, thename, 0) != 0
775 			    || completereduce(&theunit) != 0) {
776 				errors++;
777 				printexpansion = 0;
778 				mywarnx("Error in unit '%s'", thename);
779 			}
780 		}
781 		if (printexpansion) {
782 			printf("%s", thename);
783 			showunit(&theunit);
784 		} else
785 			printf("%s\t%s\n", thename, thedefn);
786 	}
787 
788 	if (errors)
789 		mywarnx("Definitions with errors: %d", errors);
790 	return (errors ? 1 : 0);
791 }
792 
793 static void
794 usage(void)
795 {
796 	fprintf(stderr,
797 	    "\nunits [-Llqv] [-f filename] [[count] from-unit to-unit]\n");
798 	fprintf(stderr, "\n    -f specify units file\n");
799 	fprintf(stderr, "    -L list units in standardized base units\n");
800 	fprintf(stderr, "    -l list units\n");
801 	fprintf(stderr, "    -q suppress prompting (quiet)\n");
802 	fprintf(stderr, "    -v print version number\n");
803 	exit(3);
804 }
805 
806 int
807 main(int argc, char **argv)
808 {
809 
810 	struct unittype have, want;
811 	char havestr[81], wantstr[81];
812 	int optchar;
813 	const char *userfile = 0;
814 	int list = 0, listexpand = 0;
815 	int quiet = 0;
816 
817 	while ((optchar = getopt(argc, argv, "lLvqf:")) != -1) {
818 		switch (optchar) {
819 		case 'l':
820 			list = 1;
821 			break;
822 		case 'L':
823 			list = 1;
824 			listexpand = 1;
825 			precision = DBL_DIG;
826 			break;
827 		case 'f':
828 			userfile = optarg;
829 			break;
830 		case 'q':
831 			quiet = 1;
832 			break;
833 		case 'v':
834 			fprintf(stderr, "\n  units version %s  Copyright (c) 1993 by Adrian Mariano\n",
835 			    VERSION);
836 			fprintf(stderr, "                    This program may be freely distributed\n");
837 			usage();
838 		default:
839 			usage();
840 			break;
841 		}
842 	}
843 
844 	argc -= optind;
845 	argv += optind;
846 
847 	if ((argc != 3 && argc != 2 && argc != 0)
848 	    || (list && argc != 0))
849 		usage();
850 
851 	if (list)
852 		errprefix = "/ ";	/* set this before reading the file */
853 
854 	readunits(userfile);
855 
856 	if (list)
857 		return listunits(listexpand);
858 
859 	if (argc == 3) {
860 		strlcpy(havestr, argv[0], sizeof(havestr));
861 		strlcat(havestr, " ", sizeof(havestr));
862 		strlcat(havestr, argv[1], sizeof(havestr));
863 		argc--;
864 		argv++;
865 		argv[0] = havestr;
866 	}
867 
868 	if (argc == 2) {
869 		strlcpy(havestr, argv[0], sizeof(havestr));
870 		strlcpy(wantstr, argv[1], sizeof(wantstr));
871 		initializeunit(&have);
872 		addunit(&have, havestr, 0);
873 		completereduce(&have);
874 		initializeunit(&want);
875 		addunit(&want, wantstr, 0);
876 		completereduce(&want);
877 		showanswer(&have, &want);
878 	}
879 	else {
880 		if (!quiet)
881 			printf("%d units, %d prefixes\n\n", unitcount,
882 			    prefixcount);
883 		for (;;) {
884 			do {
885 				initializeunit(&have);
886 				if (!quiet)
887 					printf("You have: ");
888 				if (!fgets(havestr, 80, stdin)) {
889 					if (!quiet)
890 						putchar('\n');
891 					exit(0);
892 				}
893 			} while (addunit(&have, havestr, 0) ||
894 			    completereduce(&have));
895 			do {
896 				initializeunit(&want);
897 				if (!quiet)
898 					printf("You want: ");
899 				if (!fgets(wantstr, 80, stdin)) {
900 					if (!quiet)
901 						putchar('\n');
902 					exit(0);
903 				}
904 			} while (addunit(&want, wantstr, 0) ||
905 			    completereduce(&want));
906 			showanswer(&have, &want);
907 		}
908 	}
909 	return (0);
910 }
911