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