Home | History | Annotate | Line # | Download | only in units
units.c revision 1.26
      1 /*	$NetBSD: units.c,v 1.26 2016/02/05 03:30:08 dholland Exp $	*/
      2 
      3 /*
      4  * units.c   Copyright (c) 1993 by Adrian Mariano (adrian (at) 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 *
     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
    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
    128 readerror(int linenum)
    129 {
    130 	mywarnx("Error in units file '%s' line %d", UNITSFILE, linenum);
    131 }
    132 
    133 
    134 static void
    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
    262 initializeunit(struct unittype * theunit)
    263 {
    264 	theunit->factor = 1.0;
    265 	theunit->numerator[0] = theunit->denominator[0] = NULL;
    266 }
    267 
    268 static int
    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
    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
    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
    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 > 0 && item[pos - 1] == '-') {
    459 						/* allow negative exponents */
    460 						pos--;
    461 					}
    462 					/* have an exponent */
    463 					repeat = strtol(item + pos, NULL, 10);
    464 					item[pos] = 0;
    465 					if (repeat == 0) {
    466 						/* not really the right msg */
    467 						zeroerror();
    468 						return 1;
    469 					}
    470 					if (repeat < 0) {
    471 						flipthis = 1;
    472 						repeat = -repeat;
    473 					}
    474 				}
    475 				flipthis ^= doingtop ^ flip;
    476 				for (; repeat; repeat--)
    477 					if (addsubunit(flipthis ? theunit->numerator : theunit->denominator, item))
    478 						return 1;
    479 			}
    480 			item = strtok(NULL, " *\t/\n");
    481 		}
    482 		doingtop--;
    483 		if (slash) {
    484 			scratch = slash + 1;
    485 		}
    486 		else
    487 			doingtop--;
    488 	} while (doingtop >= 0);
    489 	free(savescr);
    490 	return 0;
    491 }
    492 
    493 static int
    494 compare(const void *item1, const void *item2)
    495 {
    496 	return strcmp(*(const char * const *) item1,
    497 		      *(const char * const *) item2);
    498 }
    499 
    500 static void
    501 sortunit(struct unittype * theunit)
    502 {
    503 	const char **ptr;
    504 	int count;
    505 
    506 	for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++);
    507 	qsort(theunit->numerator, count, sizeof(char *), compare);
    508 	for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++);
    509 	qsort(theunit->denominator, count, sizeof(char *), compare);
    510 }
    511 
    512 static void
    513 cancelunit(struct unittype * theunit)
    514 {
    515 	const char **den, **num;
    516 	int comp;
    517 
    518 	den = theunit->denominator;
    519 	num = theunit->numerator;
    520 
    521 	while (*num && *den) {
    522 		comp = strcmp(*den, *num);
    523 		if (!comp) {
    524 /*      if (*den!=NULLUNIT) free(*den);
    525       if (*num!=NULLUNIT) free(*num);*/
    526 			*den++ = NULLUNIT;
    527 			*num++ = NULLUNIT;
    528 		}
    529 		else if (comp < 0)
    530 			den++;
    531 		else
    532 			num++;
    533 	}
    534 }
    535 
    536 
    537 
    538 
    539 /*
    540    Looks up the definition for the specified unit.
    541    Returns a pointer to the definition or a null pointer
    542    if the specified unit does not appear in the units table.
    543 */
    544 
    545 static char buffer[100];	/* buffer for lookupunit answers with
    546 				   prefixes */
    547 
    548 static const char *
    549 lookupunit(const char *unit)
    550 {
    551 	int i;
    552 	char *copy;
    553 
    554 	for (i = 0; i < unitcount; i++) {
    555 		if (!strcmp(unittable[i].uname, unit))
    556 			return unittable[i].uval;
    557 	}
    558 
    559 	if (unit[strlen(unit) - 1] == '^') {
    560 		copy = dupstr(unit);
    561 		copy[strlen(copy) - 1] = 0;
    562 		for (i = 0; i < unitcount; i++) {
    563 			if (!strcmp(unittable[i].uname, copy)) {
    564 				strlcpy(buffer, copy, sizeof(buffer));
    565 				free(copy);
    566 				return buffer;
    567 			}
    568 		}
    569 		free(copy);
    570 	}
    571 	if (unit[strlen(unit) - 1] == 's') {
    572 		copy = dupstr(unit);
    573 		copy[strlen(copy) - 1] = 0;
    574 		for (i = 0; i < unitcount; i++) {
    575 			if (!strcmp(unittable[i].uname, copy)) {
    576 				strlcpy(buffer, copy, sizeof(buffer));
    577 				free(copy);
    578 				return buffer;
    579 			}
    580 		}
    581 		if (copy[strlen(copy) - 1] == 'e') {
    582 			copy[strlen(copy) - 1] = 0;
    583 			for (i = 0; i < unitcount; i++) {
    584 				if (!strcmp(unittable[i].uname, copy)) {
    585 					strlcpy(buffer, copy, sizeof(buffer));
    586 					free(copy);
    587 					return buffer;
    588 				}
    589 			}
    590 		}
    591 		free(copy);
    592 	}
    593 	for (i = 0; i < prefixcount; i++) {
    594 		if (!strncmp(prefixtable[i].prefixname, unit,
    595 			strlen(prefixtable[i].prefixname))) {
    596 			unit += strlen(prefixtable[i].prefixname);
    597 			if (!strlen(unit) || lookupunit(unit)) {
    598 				strlcpy(buffer, prefixtable[i].prefixval,
    599 				    sizeof(buffer));
    600 				strlcat(buffer, " ", sizeof(buffer));
    601 				strlcat(buffer, unit, sizeof(buffer));
    602 				return buffer;
    603 			}
    604 		}
    605 	}
    606 	return 0;
    607 }
    608 
    609 
    610 
    611 /*
    612    reduces a product of symbolic units to primitive units.
    613    The three low bits are used to return flags:
    614 
    615      bit 0 (1) set on if reductions were performed without error.
    616      bit 1 (2) set on if no reductions are performed.
    617      bit 2 (4) set on if an unknown unit is discovered.
    618 */
    619 
    620 
    621 #define ERROR 4
    622 
    623 static int
    624 reduceproduct(struct unittype * theunit, int flip)
    625 {
    626 
    627 	const char *toadd;
    628 	const char **product;
    629 	int didsomething = 2;
    630 
    631 	if (flip)
    632 		product = theunit->denominator;
    633 	else
    634 		product = theunit->numerator;
    635 
    636 	for (; *product; product++) {
    637 
    638 		for (;;) {
    639 			if (!strlen(*product))
    640 				break;
    641 			toadd = lookupunit(*product);
    642 			if (!toadd) {
    643 				mywarnx("Unknown unit '%s'", *product);
    644 				return ERROR;
    645 			}
    646 			if (strchr(toadd, PRIMITIVECHAR))
    647 				break;
    648 			didsomething = 1;
    649 			if (*product != NULLUNIT) {
    650 				free(__UNCONST(*product));
    651 				*product = NULLUNIT;
    652 			}
    653 			if (addunit(theunit, toadd, flip))
    654 				return ERROR;
    655 		}
    656 	}
    657 	return didsomething;
    658 }
    659 
    660 
    661 /*
    662    Reduces numerator and denominator of the specified unit.
    663    Returns 0 on success, or 1 on unknown unit error.
    664 */
    665 
    666 static int
    667 reduceunit(struct unittype * theunit)
    668 {
    669 	int ret;
    670 
    671 	ret = 1;
    672 	while (ret & 1) {
    673 		ret = reduceproduct(theunit, 0) | reduceproduct(theunit, 1);
    674 		if (ret & 4)
    675 			return 1;
    676 	}
    677 	return 0;
    678 }
    679 
    680 static int
    681 compareproducts(const char **one, const char **two)
    682 {
    683 	while (*one || *two) {
    684 		if (!*one && *two != NULLUNIT)
    685 			return 1;
    686 		if (!*two && *one != NULLUNIT)
    687 			return 1;
    688 		if (*one == NULLUNIT)
    689 			one++;
    690 		else if (*two == NULLUNIT)
    691 			two++;
    692 		else if (*one && *two && strcmp(*one, *two))
    693 			return 1;
    694 		else
    695 			one++, two++;
    696 	}
    697 	return 0;
    698 }
    699 
    700 
    701 /* Return zero if units are compatible, nonzero otherwise */
    702 
    703 static int
    704 compareunits(struct unittype * first, struct unittype * second)
    705 {
    706 	return
    707 	compareproducts(first->numerator, second->numerator) ||
    708 	compareproducts(first->denominator, second->denominator);
    709 }
    710 
    711 static int
    712 compareunitsreciprocal(struct unittype * first, struct unittype * second)
    713 {
    714 	return
    715 	compareproducts(first->numerator, second->denominator) ||
    716 	compareproducts(first->denominator, second->numerator);
    717 }
    718 
    719 
    720 static int
    721 completereduce(struct unittype * unit)
    722 {
    723 	if (reduceunit(unit))
    724 		return 1;
    725 	sortunit(unit);
    726 	cancelunit(unit);
    727 	return 0;
    728 }
    729 
    730 
    731 static void
    732 showanswer(struct unittype * have, struct unittype * want)
    733 {
    734 	if (compareunits(have, want)) {
    735 		if (compareunitsreciprocal(have, want)) {
    736 			printf("conformability error\n");
    737 			showunit(have);
    738 			showunit(want);
    739 		} else {
    740 			printf("\treciprocal conversion\n");
    741 			printf("\t* %.*g\n\t/ %.*g\n",
    742 			    precision, 1 / (have->factor * want->factor),
    743 			    precision, want->factor * have->factor);
    744 		}
    745 	}
    746 	else
    747 		printf("\t* %.*g\n\t/ %.*g\n",
    748 		    precision, have->factor / want->factor,
    749 		    precision, want->factor / have->factor);
    750 }
    751 
    752 static int
    753 listunits(int expand)
    754 {
    755 	struct unittype theunit;
    756 	const char *thename;
    757 	const char *thedefn;
    758 	int errors = 0;
    759 	int i;
    760 	int printexpansion;
    761 
    762 	/*
    763 	 * send error and warning messages to stdout,
    764 	 * and make them look like comments.
    765 	 */
    766 	errprefix = "/ ";
    767 
    768 #if 0 /* debug */
    769 	printf("/ expand=%d precision=%d unitcount=%d prefixcount=%d\n",
    770 	    expand, precision, unitcount, prefixcount);
    771 #endif
    772 
    773 	/* 1. Dump all primitive units, e.g. "m !a!", "kg !b!", ... */
    774 	printf("/ Primitive units\n");
    775 	for (i = 0; i < unitcount; i++) {
    776 		thename = unittable[i].uname;
    777 		thedefn = unittable[i].uval;
    778 		if (thedefn[0] == PRIMITIVECHAR) {
    779 			printf("%s\t%s\n", thename, thedefn);
    780 		}
    781 	}
    782 
    783 	/* 2. Dump all prefixes, e.g. "yotta- 1e24", "zetta- 1e21", ... */
    784 	printf("/ Prefixes\n");
    785 	for (i = 0; i < prefixcount; i++) {
    786 		printexpansion = expand;
    787 		thename = prefixtable[i].prefixname;
    788 		thedefn = prefixtable[i].prefixval;
    789 		if (expand) {
    790 			/*
    791 			 * prefix names are sometimes identical to unit
    792 			 * names, so we have to expand thedefn instead of
    793 			 * expanding thename.
    794 			 */
    795 			initializeunit(&theunit);
    796 			if (addunit(&theunit, thedefn, 0) != 0
    797 			    || completereduce(&theunit) != 0) {
    798 				errors++;
    799 				printexpansion = 0;
    800 				mywarnx("Error in prefix '%s-'", thename);
    801 			}
    802 		}
    803 		if (printexpansion) {
    804 			printf("%s-", thename);
    805 			showunit(&theunit);
    806 		} else
    807 			printf("%s-\t%s\n", thename, thedefn);
    808 	}
    809 
    810 	/* 3. Dump all other units. */
    811 	printf("/ Other units\n");
    812 	for (i = 0; i < unitcount; i++) {
    813 		printexpansion = expand;
    814 		thename = unittable[i].uname;
    815 		thedefn = unittable[i].uval;
    816 		if (thedefn[0] == PRIMITIVECHAR)
    817 			continue;
    818 		if (expand) {
    819 			/*
    820 			 * expand thename, not thedefn, so that
    821 			 * we can catch errors in the name itself.
    822 			 * e.g. a name that contains a hyphen
    823 			 * will be interpreted as multiplication.
    824 			 */
    825 			initializeunit(&theunit);
    826 			if (addunit(&theunit, thename, 0) != 0
    827 			    || completereduce(&theunit) != 0) {
    828 				errors++;
    829 				printexpansion = 0;
    830 				mywarnx("Error in unit '%s'", thename);
    831 			}
    832 		}
    833 		if (printexpansion) {
    834 			printf("%s", thename);
    835 			showunit(&theunit);
    836 		} else
    837 			printf("%s\t%s\n", thename, thedefn);
    838 	}
    839 
    840 	if (errors)
    841 		mywarnx("Definitions with errors: %d", errors);
    842 	return (errors ? 1 : 0);
    843 }
    844 
    845 static void
    846 usage(void)
    847 {
    848 	fprintf(stderr,
    849 	    "\nunits [-Llqv] [-f filename] [[count] from-unit to-unit]\n");
    850 	fprintf(stderr, "\n    -f specify units file\n");
    851 	fprintf(stderr, "    -L list units in standardized base units\n");
    852 	fprintf(stderr, "    -l list units\n");
    853 	fprintf(stderr, "    -q suppress prompting (quiet)\n");
    854 	fprintf(stderr, "    -v print version number\n");
    855 	exit(3);
    856 }
    857 
    858 int
    859 main(int argc, char **argv)
    860 {
    861 
    862 	struct unittype have, want;
    863 	char havestr[81], wantstr[81];
    864 	int optchar;
    865 	const char *userfile = 0;
    866 	int list = 0, listexpand = 0;
    867 	int quiet = 0;
    868 
    869 	while ((optchar = getopt(argc, argv, "lLvqf:")) != -1) {
    870 		switch (optchar) {
    871 		case 'l':
    872 			list = 1;
    873 			break;
    874 		case 'L':
    875 			list = 1;
    876 			listexpand = 1;
    877 			precision = DBL_DIG;
    878 			break;
    879 		case 'f':
    880 			userfile = optarg;
    881 			break;
    882 		case 'q':
    883 			quiet = 1;
    884 			break;
    885 		case 'v':
    886 			fprintf(stderr, "\n  units version %s  Copyright (c) 1993 by Adrian Mariano\n",
    887 			    VERSION);
    888 			fprintf(stderr, "                    This program may be freely distributed\n");
    889 			usage();
    890 		default:
    891 			usage();
    892 			break;
    893 		}
    894 	}
    895 
    896 	argc -= optind;
    897 	argv += optind;
    898 
    899 	if ((argc != 3 && argc != 2 && argc != 0)
    900 	    || (list && argc != 0))
    901 		usage();
    902 
    903 	if (list)
    904 		errprefix = "/ ";	/* set this before reading the file */
    905 
    906 	readunits(userfile);
    907 
    908 	if (list)
    909 		return listunits(listexpand);
    910 
    911 	if (argc == 3) {
    912 		strlcpy(havestr, argv[0], sizeof(havestr));
    913 		strlcat(havestr, " ", sizeof(havestr));
    914 		strlcat(havestr, argv[1], sizeof(havestr));
    915 		argc--;
    916 		argv++;
    917 		argv[0] = havestr;
    918 	}
    919 
    920 	if (argc == 2) {
    921 		strlcpy(havestr, argv[0], sizeof(havestr));
    922 		strlcpy(wantstr, argv[1], sizeof(wantstr));
    923 		initializeunit(&have);
    924 		addunit(&have, havestr, 0);
    925 		completereduce(&have);
    926 		initializeunit(&want);
    927 		addunit(&want, wantstr, 0);
    928 		completereduce(&want);
    929 		showanswer(&have, &want);
    930 	}
    931 	else {
    932 		if (!quiet)
    933 			printf("%d units, %d prefixes\n\n", unitcount,
    934 			    prefixcount);
    935 		for (;;) {
    936 			do {
    937 				initializeunit(&have);
    938 				if (!quiet)
    939 					printf("You have: ");
    940 				if (!fgets(havestr, 80, stdin)) {
    941 					if (!quiet)
    942 						putchar('\n');
    943 					exit(0);
    944 				}
    945 			} while (addunit(&have, havestr, 0) ||
    946 			    completereduce(&have));
    947 			do {
    948 				initializeunit(&want);
    949 				if (!quiet)
    950 					printf("You want: ");
    951 				if (!fgets(wantstr, 80, stdin)) {
    952 					if (!quiet)
    953 						putchar('\n');
    954 					exit(0);
    955 				}
    956 			} while (addunit(&want, wantstr, 0) ||
    957 			    completereduce(&want));
    958 			showanswer(&have, &want);
    959 		}
    960 	}
    961 	return (0);
    962 }
    963