Home | History | Annotate | Line # | Download | only in units
      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 (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 > 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
    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
    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
    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 *
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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