xref: /netbsd-src/usr.bin/units/units.c (revision ac57bd7b30433daa8b53bdac1eff0a6600a188b1)
1*ac57bd7bSdholland /*	$NetBSD: units.c,v 1.27 2016/02/05 03:32:49 dholland Exp $	*/
269aab315Sthorpej 
317012381Scgd /*
417012381Scgd  * units.c   Copyright (c) 1993 by Adrian Mariano (adrian@cam.cornell.edu)
517012381Scgd  *
617012381Scgd  * Redistribution and use in source and binary forms, with or without
717012381Scgd  * modification, are permitted provided that the following conditions
817012381Scgd  * are met:
917012381Scgd  * 1. Redistributions of source code must retain the above copyright
1017012381Scgd  *    notice, this list of conditions and the following disclaimer.
1117012381Scgd  * 2. The name of the author may not be used to endorse or promote products
1242f840d2Sjtc  *    derived from this software without specific prior written permission.
1317012381Scgd  * Disclaimer:  This software is provided by the author "as is".  The author
1417012381Scgd  * shall not be liable for any damages caused in any way by this software.
1517012381Scgd  *
1617012381Scgd  * I would appreciate (though I do not require) receiving a copy of any
1717012381Scgd  * improvements you might make to this program.
1817012381Scgd  */
1917012381Scgd 
200561f14bSdholland #include <assert.h>
21d9a436e9Sjtc #include <ctype.h>
222c875534Slukem #include <err.h>
23c6933fcaSapb #include <float.h>
2417012381Scgd #include <stdio.h>
2517012381Scgd #include <string.h>
2617012381Scgd #include <stdlib.h>
27db49a933Sperry #include <unistd.h>
2817012381Scgd 
2917012381Scgd #include "pathnames.h"
3017012381Scgd 
3117012381Scgd #define VERSION "1.0"
3217012381Scgd 
3317012381Scgd #ifndef UNITSFILE
3417012381Scgd #define UNITSFILE _PATH_UNITSLIB
3517012381Scgd #endif
3617012381Scgd 
3717012381Scgd #define MAXUNITS 1000
3817012381Scgd #define MAXPREFIXES 50
3917012381Scgd 
4017012381Scgd #define MAXSUBUNITS 500
4117012381Scgd 
4217012381Scgd #define PRIMITIVECHAR '!'
4317012381Scgd 
44c6933fcaSapb static int precision = 8;		/* for printf with "%.*g" format */
45c6933fcaSapb 
46c6933fcaSapb static const char *errprefix = NULL;	/* if not NULL, then prepend this
47c6933fcaSapb 					 * to error messages and send them to
48c6933fcaSapb 					 * stdout instead of stderr.
49c6933fcaSapb 					 */
50c6933fcaSapb 
511ed141bbSjoerg static const char *powerstring = "^";
5217012381Scgd 
531ed141bbSjoerg static struct {
5495f5c18eSlukem 	const char *uname;
5595f5c18eSlukem 	const char *uval;
5617012381Scgd }      unittable[MAXUNITS];
5717012381Scgd 
5817012381Scgd struct unittype {
5995f5c18eSlukem 	const char *numerator[MAXSUBUNITS];
6095f5c18eSlukem 	const char *denominator[MAXSUBUNITS];
6117012381Scgd 	double factor;
6217012381Scgd };
6317012381Scgd 
6417012381Scgd struct {
6595f5c18eSlukem 	const char *prefixname;
6695f5c18eSlukem 	const char *prefixval;
6717012381Scgd }      prefixtable[MAXPREFIXES];
6817012381Scgd 
6917012381Scgd 
701ed141bbSjoerg static const char *NULLUNIT = "";
7117012381Scgd 
721ed141bbSjoerg static int unitcount;
731ed141bbSjoerg static int prefixcount;
7417012381Scgd 
7517012381Scgd 
761ed141bbSjoerg static int	addsubunit(const char *[], const char *);
771ed141bbSjoerg static int	addunit(struct unittype *, const char *, int);
781ed141bbSjoerg static void	cancelunit(struct unittype *);
791ed141bbSjoerg static int	compare(const void *, const void *);
801ed141bbSjoerg static int	compareproducts(const char **, const char **);
811ed141bbSjoerg static int	compareunits(struct unittype *, struct unittype *);
821ed141bbSjoerg static int	compareunitsreciprocal(struct unittype *, struct unittype *);
831ed141bbSjoerg static int	completereduce(struct unittype *);
841ed141bbSjoerg static void	initializeunit(struct unittype *);
851ed141bbSjoerg static void	readerror(int);
861ed141bbSjoerg static void	readunits(const char *);
871ed141bbSjoerg static int	reduceproduct(struct unittype *, int);
881ed141bbSjoerg static int	reduceunit(struct unittype *);
891ed141bbSjoerg static void	showanswer(struct unittype *, struct unittype *);
901ed141bbSjoerg static void	showunit(struct unittype *);
911ed141bbSjoerg static void	sortunit(struct unittype *);
921ed141bbSjoerg __dead static void	usage(void);
931ed141bbSjoerg static void	zeroerror(void);
941ed141bbSjoerg static char   *dupstr(const char *);
951ed141bbSjoerg static const char *lookupunit(const char *);
962c875534Slukem 
971ed141bbSjoerg static char *
dupstr(const char * str)9895f5c18eSlukem dupstr(const char *str)
9917012381Scgd {
10017012381Scgd 	char *ret;
10117012381Scgd 
1024005e1b9Sitojun 	ret = strdup(str);
1032c875534Slukem 	if (!ret)
1042c875534Slukem 		err(3, "Memory allocation error");
10517012381Scgd 	return (ret);
10617012381Scgd }
10717012381Scgd 
10817012381Scgd 
109d9c7ee5bSjoerg static __printflike(1, 2) void
mywarnx(const char * fmt,...)110c6933fcaSapb mywarnx(const char *fmt, ...)
111c6933fcaSapb {
112c6933fcaSapb 	va_list args;
113c6933fcaSapb 
114c6933fcaSapb 	va_start(args, fmt);
115c6933fcaSapb 	if (errprefix) {
116c6933fcaSapb 		/* warn to stdout, with errprefix prepended */
117c6933fcaSapb 		printf("%s", errprefix);
118c6933fcaSapb 		vprintf(fmt, args);
119c6933fcaSapb 		printf("%s", "\n");
120c6933fcaSapb 	} else {
121c6933fcaSapb 		/* warn to stderr */
122c6933fcaSapb 		vwarnx(fmt, args);
123c6933fcaSapb 	}
124c6933fcaSapb 	va_end(args);
125c6933fcaSapb }
126c6933fcaSapb 
127c6933fcaSapb static void
readerror(int linenum)12817012381Scgd readerror(int linenum)
12917012381Scgd {
130c6933fcaSapb 	mywarnx("Error in units file '%s' line %d", UNITSFILE, linenum);
13117012381Scgd }
13217012381Scgd 
13317012381Scgd 
1341ed141bbSjoerg static void
readunits(const char * userfile)13595f5c18eSlukem readunits(const char *userfile)
13617012381Scgd {
13717012381Scgd 	FILE *unitfile;
13817012381Scgd 	char line[80], *lineptr;
13955f985ffSapb 	int len, linenum, i, isdup;
14017012381Scgd 
14117012381Scgd 	unitcount = 0;
14217012381Scgd 	linenum = 0;
14317012381Scgd 
14417012381Scgd 	if (userfile) {
14517012381Scgd 		unitfile = fopen(userfile, "rt");
1462c875534Slukem 		if (!unitfile)
1472c875534Slukem 			err(1, "Unable to open units file '%s'", userfile);
14817012381Scgd 	}
14917012381Scgd 	else {
15017012381Scgd 		unitfile = fopen(UNITSFILE, "rt");
15117012381Scgd 		if (!unitfile) {
15217012381Scgd 			char *direc, *env;
15317012381Scgd 			char filename[1000];
15417012381Scgd 			char separator[2];
15517012381Scgd 
15617012381Scgd 			env = getenv("PATH");
15717012381Scgd 			if (env) {
15817012381Scgd 				if (strchr(env, ';'))
1594005e1b9Sitojun 					strlcpy(separator, ";",
1604005e1b9Sitojun 					    sizeof(separator));
16117012381Scgd 				else
1624005e1b9Sitojun 					strlcpy(separator, ":",
1634005e1b9Sitojun 					    sizeof(separator));
16417012381Scgd 				direc = strtok(env, separator);
16517012381Scgd 				while (direc) {
1664005e1b9Sitojun 					strlcpy(filename, "", sizeof(filename));
1674005e1b9Sitojun 					strlcat(filename, direc,
1684005e1b9Sitojun 					    sizeof(filename));
1694005e1b9Sitojun 					strlcat(filename, "/",
1704005e1b9Sitojun 					    sizeof(filename));
1714005e1b9Sitojun 					strlcat(filename, UNITSFILE,
1724005e1b9Sitojun 					    sizeof(filename));
17317012381Scgd 					unitfile = fopen(filename, "rt");
17417012381Scgd 					if (unitfile)
17517012381Scgd 						break;
17617012381Scgd 					direc = strtok(NULL, separator);
17717012381Scgd 				}
17817012381Scgd 			}
1792c875534Slukem 			if (!unitfile)
1802c875534Slukem 				errx(1, "Can't find units file '%s'",
18117012381Scgd 				    UNITSFILE);
18217012381Scgd 		}
18317012381Scgd 	}
18417012381Scgd 	while (!feof(unitfile)) {
18517012381Scgd 		if (!fgets(line, 79, unitfile))
18617012381Scgd 			break;
18717012381Scgd 		linenum++;
18817012381Scgd 		lineptr = line;
18917012381Scgd 		if (*lineptr == '/')
19017012381Scgd 			continue;
19117012381Scgd 		lineptr += strspn(lineptr, " \n\t");
19217012381Scgd 		len = strcspn(lineptr, " \n\t");
19317012381Scgd 		lineptr[len] = 0;
19417012381Scgd 		if (!strlen(lineptr))
19517012381Scgd 			continue;
19617012381Scgd 		if (lineptr[strlen(lineptr) - 1] == '-') { /* it's a prefix */
19717012381Scgd 			if (prefixcount == MAXPREFIXES) {
198c6933fcaSapb 				mywarnx(
199c6933fcaSapb 			"Memory for prefixes exceeded in line %d",
20017012381Scgd 					linenum);
20117012381Scgd 				continue;
20217012381Scgd 			}
20317012381Scgd 			lineptr[strlen(lineptr) - 1] = 0;
20455f985ffSapb 			for (isdup = 0, i = 0; i < prefixcount; i++) {
20555f985ffSapb 				if (!strcmp(prefixtable[i].prefixname,
20655f985ffSapb 				    lineptr)) {
20755f985ffSapb 					isdup = 1;
20855f985ffSapb 					break;
20955f985ffSapb 				}
21055f985ffSapb 			}
21155f985ffSapb 			if (isdup) {
212c6933fcaSapb 				mywarnx(
2132c875534Slukem 			"Redefinition of prefix '%s' on line %d ignored",
21417012381Scgd 				    lineptr, linenum);
21517012381Scgd 				continue;
21617012381Scgd 			}
21755f985ffSapb 			prefixtable[prefixcount].prefixname = dupstr(lineptr);
21817012381Scgd 			lineptr += len + 1;
21917012381Scgd 			if (!strlen(lineptr)) {
22017012381Scgd 				readerror(linenum);
22117012381Scgd 				continue;
22217012381Scgd 			}
22317012381Scgd 			lineptr += strspn(lineptr, " \n\t");
22417012381Scgd 			len = strcspn(lineptr, "\n\t");
22517012381Scgd 			lineptr[len] = 0;
22617012381Scgd 			prefixtable[prefixcount++].prefixval = dupstr(lineptr);
22717012381Scgd 		}
22817012381Scgd 		else {		/* it's not a prefix */
22917012381Scgd 			if (unitcount == MAXUNITS) {
230c6933fcaSapb 				mywarnx("Memory for units exceeded in line %d",
23117012381Scgd 				    linenum);
23217012381Scgd 				continue;
23317012381Scgd 			}
23455f985ffSapb 			for (isdup = 0, i = 0; i < unitcount; i++) {
23517012381Scgd 				if (!strcmp(unittable[i].uname, lineptr)) {
23655f985ffSapb 					isdup = 1;
23755f985ffSapb 					break;
23855f985ffSapb 				}
23955f985ffSapb 			}
24055f985ffSapb 			if (isdup) {
241c6933fcaSapb 				mywarnx(
2422c875534Slukem 				"Redefinition of unit '%s' on line %d ignored",
24317012381Scgd 				    lineptr, linenum);
24417012381Scgd 				continue;
24517012381Scgd 			}
24655f985ffSapb 			unittable[unitcount].uname = dupstr(lineptr);
24717012381Scgd 			lineptr += len + 1;
24817012381Scgd 			lineptr += strspn(lineptr, " \n\t");
24917012381Scgd 			if (!strlen(lineptr)) {
25017012381Scgd 				readerror(linenum);
25117012381Scgd 				continue;
25217012381Scgd 			}
25317012381Scgd 			len = strcspn(lineptr, "\n\t");
25417012381Scgd 			lineptr[len] = 0;
25517012381Scgd 			unittable[unitcount++].uval = dupstr(lineptr);
25617012381Scgd 		}
25717012381Scgd 	}
25817012381Scgd 	fclose(unitfile);
25917012381Scgd }
26017012381Scgd 
2611ed141bbSjoerg static void
initializeunit(struct unittype * theunit)26217012381Scgd initializeunit(struct unittype * theunit)
26317012381Scgd {
26417012381Scgd 	theunit->factor = 1.0;
26517012381Scgd 	theunit->numerator[0] = theunit->denominator[0] = NULL;
26617012381Scgd }
26717012381Scgd 
2681ed141bbSjoerg static int
addsubunit(const char * product[],const char * toadd)26995f5c18eSlukem addsubunit(const char *product[], const char *toadd)
27017012381Scgd {
27195f5c18eSlukem 	const char **ptr;
27217012381Scgd 
27317012381Scgd 	for (ptr = product; *ptr && *ptr != NULLUNIT; ptr++);
27417012381Scgd 	if (ptr >= product + MAXSUBUNITS) {
275c6933fcaSapb 		mywarnx("Memory overflow in unit reduction");
27617012381Scgd 		return 1;
27717012381Scgd 	}
27817012381Scgd 	if (!*ptr)
27917012381Scgd 		*(ptr + 1) = 0;
28017012381Scgd 	*ptr = dupstr(toadd);
28117012381Scgd 	return 0;
28217012381Scgd }
28317012381Scgd 
2841ed141bbSjoerg static void
showunit(struct unittype * theunit)28517012381Scgd showunit(struct unittype * theunit)
28617012381Scgd {
28795f5c18eSlukem 	const char **ptr;
28817012381Scgd 	int printedslash;
28917012381Scgd 	int counter = 1;
29017012381Scgd 
291c6933fcaSapb 	printf("\t%.*g", precision, theunit->factor);
29217012381Scgd 	for (ptr = theunit->numerator; *ptr; ptr++) {
29317012381Scgd 		if (ptr > theunit->numerator && **ptr &&
29417012381Scgd 		    !strcmp(*ptr, *(ptr - 1)))
29517012381Scgd 			counter++;
29617012381Scgd 		else {
29717012381Scgd 			if (counter > 1)
29817012381Scgd 				printf("%s%d", powerstring, counter);
29917012381Scgd 			if (**ptr)
30017012381Scgd 				printf(" %s", *ptr);
30117012381Scgd 			counter = 1;
30217012381Scgd 		}
30317012381Scgd 	}
30417012381Scgd 	if (counter > 1)
30517012381Scgd 		printf("%s%d", powerstring, counter);
30617012381Scgd 	counter = 1;
30717012381Scgd 	printedslash = 0;
30817012381Scgd 	for (ptr = theunit->denominator; *ptr; ptr++) {
30917012381Scgd 		if (ptr > theunit->denominator && **ptr &&
31017012381Scgd 		    !strcmp(*ptr, *(ptr - 1)))
31117012381Scgd 			counter++;
31217012381Scgd 		else {
31317012381Scgd 			if (counter > 1)
31417012381Scgd 				printf("%s%d", powerstring, counter);
31517012381Scgd 			if (**ptr) {
31617012381Scgd 				if (!printedslash)
31717012381Scgd 					printf(" /");
31817012381Scgd 				printedslash = 1;
31917012381Scgd 				printf(" %s", *ptr);
32017012381Scgd 			}
32117012381Scgd 			counter = 1;
32217012381Scgd 		}
32317012381Scgd 	}
32417012381Scgd 	if (counter > 1)
32517012381Scgd 		printf("%s%d", powerstring, counter);
32617012381Scgd 	printf("\n");
32717012381Scgd }
32817012381Scgd 
3291ed141bbSjoerg static void
zeroerror(void)330d34c2845Smatt zeroerror(void)
33117012381Scgd {
332c6933fcaSapb 	mywarnx("Unit reduces to zero");
33317012381Scgd }
33417012381Scgd 
33517012381Scgd /*
33617012381Scgd    Adds the specified string to the unit.
33717012381Scgd    Flip is 0 for adding normally, 1 for adding reciprocal.
33817012381Scgd 
33917012381Scgd    Returns 0 for successful addition, nonzero on error.
34017012381Scgd */
34117012381Scgd 
3421ed141bbSjoerg static int
addunit(struct unittype * theunit,const char * toadd,int flip)34395f5c18eSlukem addunit(struct unittype * theunit, const char *toadd, int flip)
34417012381Scgd {
34517012381Scgd 	char *scratch, *savescr;
34617012381Scgd 	char *item;
347d9a436e9Sjtc 	char *divider, *slash;
3480561f14bSdholland 	char *minus;
3490561f14bSdholland 	size_t pos, len;
35017012381Scgd 	int doingtop;
35117012381Scgd 
35217012381Scgd 	savescr = scratch = dupstr(toadd);
3530561f14bSdholland 
3540561f14bSdholland 	/*
3550561f14bSdholland 	 * "foot-pound" is the same as "foot pound". But don't
3560561f14bSdholland 	 * trash minus signs on numbers.
3570561f14bSdholland 	 *
3580561f14bSdholland 	 * 20160204 dholland: this used to let through only minus
3590561f14bSdholland 	 * signs at the beginning of the string or in the middle of a
3600561f14bSdholland 	 * floating constant (e.g. 3.6e-5), and a minus sign at the
3610561f14bSdholland 	 * beginning of the string failed further on. I have changed
3620561f14bSdholland 	 * it so any minus sign before a digit (or decimal point) is
3630561f14bSdholland 	 * treated as going with that digit.
3640561f14bSdholland 	 *
3650561f14bSdholland 	 * Note that this changed the interpretation of certain
3660561f14bSdholland 	 * marginally valid inputs like "3 N-5 s"; that used to be
3670561f14bSdholland 	 * interpreted as "3 N 5 s" or 15 N s, but now it reads as
3680561f14bSdholland 	 * "3 N -5 s" or -15 N s. However, it also makes negative
3690561f14bSdholland 	 * exponents on units work, which used to be silently trashed.
3700561f14bSdholland 	 */
3710561f14bSdholland 	for (minus = scratch + 1; *minus; minus++) {
3720561f14bSdholland 		if (*minus != '-') {
3730561f14bSdholland 			continue;
3740561f14bSdholland 		}
3750561f14bSdholland 		if (strchr(".0123456789", *(minus + 1))) {
3760561f14bSdholland 			continue;
3770561f14bSdholland 		}
3780561f14bSdholland 		*minus = ' ';
3790561f14bSdholland 	}
3800561f14bSdholland 
3810561f14bSdholland 	/* Process up to the next / in one go. */
3820561f14bSdholland 
38317012381Scgd 	slash = strchr(scratch, '/');
38417012381Scgd 	if (slash)
38517012381Scgd 		*slash = 0;
38617012381Scgd 	doingtop = 1;
38717012381Scgd 	do {
38817012381Scgd 		item = strtok(scratch, " *\t\n/");
38917012381Scgd 		while (item) {
3900561f14bSdholland 			if ((*item == '-' && strchr("0123456789.", *(item+1)))
3910561f14bSdholland 			    || strchr("0123456789.", *item)) {
3920561f14bSdholland 
393fd02783eSapb 				/* item starts with a number */
394fd02783eSapb 				char *endptr;
39517012381Scgd 				double num;
39617012381Scgd 
39717012381Scgd 				divider = strchr(item, '|');
39817012381Scgd 				if (divider) {
39917012381Scgd 					*divider = 0;
400fd02783eSapb 					num = strtod(item, &endptr);
40117012381Scgd 					if (!num) {
40217012381Scgd 						zeroerror();
40317012381Scgd 						return 1;
40417012381Scgd 					}
405fd02783eSapb 					if (endptr != divider) {
406fd02783eSapb 						/* "6foo|2" is an error */
407c6933fcaSapb 						mywarnx("Junk before '|'");
408fd02783eSapb 						return 1;
409fd02783eSapb 					}
41017012381Scgd 					if (doingtop ^ flip)
41117012381Scgd 						theunit->factor *= num;
41217012381Scgd 					else
41317012381Scgd 						theunit->factor /= num;
414fd02783eSapb 					num = strtod(divider + 1, &endptr);
41517012381Scgd 					if (!num) {
41617012381Scgd 						zeroerror();
41717012381Scgd 						return 1;
41817012381Scgd 					}
41917012381Scgd 					if (doingtop ^ flip)
42017012381Scgd 						theunit->factor /= num;
42117012381Scgd 					else
42217012381Scgd 						theunit->factor *= num;
423fd02783eSapb 					if (*endptr) {
424fd02783eSapb 						/* "6|2foo" is like "6|2 foo" */
425fd02783eSapb 						item = endptr;
426fd02783eSapb 						continue;
427fd02783eSapb 					}
42817012381Scgd 				}
42917012381Scgd 				else {
430fd02783eSapb 					num = strtod(item, &endptr);
43117012381Scgd 					if (!num) {
43217012381Scgd 						zeroerror();
43317012381Scgd 						return 1;
43417012381Scgd 					}
43517012381Scgd 					if (doingtop ^ flip)
43617012381Scgd 						theunit->factor *= num;
43717012381Scgd 					else
43817012381Scgd 						theunit->factor /= num;
439fd02783eSapb 					if (*endptr) {
440fd02783eSapb 						/* "3foo" is like "3 foo" */
441fd02783eSapb 						item = endptr;
442fd02783eSapb 						continue;
443fd02783eSapb 					}
44417012381Scgd 				}
44517012381Scgd 			}
44617012381Scgd 			else {	/* item is not a number */
44717012381Scgd 				int repeat = 1;
4480561f14bSdholland 				int flipthis = 0;
44917012381Scgd 
4500561f14bSdholland 				pos = len = strlen(item);
4510561f14bSdholland 				assert(pos > 0);
4520561f14bSdholland 				while (strchr("0123456789", item[pos - 1])) {
4530561f14bSdholland 					pos--;
4540561f14bSdholland 					/* string began with non-digit */
4550561f14bSdholland 					assert(pos > 0);
45617012381Scgd 				}
4570561f14bSdholland 				if (pos < len) {
458*ac57bd7bSdholland 					if (pos > 1 && item[pos - 1] == '-' &&
459*ac57bd7bSdholland 					    item[pos - 2] == '^') {
4600561f14bSdholland 						/* allow negative exponents */
4610561f14bSdholland 						pos--;
4620561f14bSdholland 					}
4630561f14bSdholland 					/* have an exponent */
4640561f14bSdholland 					repeat = strtol(item + pos, NULL, 10);
4650561f14bSdholland 					item[pos] = 0;
4660561f14bSdholland 					if (repeat == 0) {
4670561f14bSdholland 						/* not really the right msg */
4680561f14bSdholland 						zeroerror();
4690561f14bSdholland 						return 1;
4700561f14bSdholland 					}
4710561f14bSdholland 					if (repeat < 0) {
4720561f14bSdholland 						flipthis = 1;
4730561f14bSdholland 						repeat = -repeat;
4740561f14bSdholland 					}
4750561f14bSdholland 				}
4760561f14bSdholland 				flipthis ^= doingtop ^ flip;
47717012381Scgd 				for (; repeat; repeat--)
4780561f14bSdholland 					if (addsubunit(flipthis ? theunit->numerator : theunit->denominator, item))
47917012381Scgd 						return 1;
48017012381Scgd 			}
48117012381Scgd 			item = strtok(NULL, " *\t/\n");
48217012381Scgd 		}
48317012381Scgd 		doingtop--;
48417012381Scgd 		if (slash) {
48517012381Scgd 			scratch = slash + 1;
48617012381Scgd 		}
48717012381Scgd 		else
48817012381Scgd 			doingtop--;
48917012381Scgd 	} while (doingtop >= 0);
49017012381Scgd 	free(savescr);
49117012381Scgd 	return 0;
49217012381Scgd }
49317012381Scgd 
4941ed141bbSjoerg static int
compare(const void * item1,const void * item2)49517012381Scgd compare(const void *item1, const void *item2)
49617012381Scgd {
49795f5c18eSlukem 	return strcmp(*(const char * const *) item1,
49895f5c18eSlukem 		      *(const char * const *) item2);
49917012381Scgd }
50017012381Scgd 
5011ed141bbSjoerg static void
sortunit(struct unittype * theunit)50217012381Scgd sortunit(struct unittype * theunit)
50317012381Scgd {
50495f5c18eSlukem 	const char **ptr;
50517012381Scgd 	int count;
50617012381Scgd 
50717012381Scgd 	for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++);
50817012381Scgd 	qsort(theunit->numerator, count, sizeof(char *), compare);
50917012381Scgd 	for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++);
51017012381Scgd 	qsort(theunit->denominator, count, sizeof(char *), compare);
51117012381Scgd }
51217012381Scgd 
5131ed141bbSjoerg static void
cancelunit(struct unittype * theunit)51417012381Scgd cancelunit(struct unittype * theunit)
51517012381Scgd {
51695f5c18eSlukem 	const char **den, **num;
51717012381Scgd 	int comp;
51817012381Scgd 
51917012381Scgd 	den = theunit->denominator;
52017012381Scgd 	num = theunit->numerator;
52117012381Scgd 
52217012381Scgd 	while (*num && *den) {
52317012381Scgd 		comp = strcmp(*den, *num);
52417012381Scgd 		if (!comp) {
52517012381Scgd /*      if (*den!=NULLUNIT) free(*den);
52617012381Scgd       if (*num!=NULLUNIT) free(*num);*/
52717012381Scgd 			*den++ = NULLUNIT;
52817012381Scgd 			*num++ = NULLUNIT;
52917012381Scgd 		}
53017012381Scgd 		else if (comp < 0)
53117012381Scgd 			den++;
53217012381Scgd 		else
53317012381Scgd 			num++;
53417012381Scgd 	}
53517012381Scgd }
53617012381Scgd 
53717012381Scgd 
53817012381Scgd 
53917012381Scgd 
54017012381Scgd /*
54117012381Scgd    Looks up the definition for the specified unit.
54217012381Scgd    Returns a pointer to the definition or a null pointer
54317012381Scgd    if the specified unit does not appear in the units table.
54417012381Scgd */
54517012381Scgd 
54617012381Scgd static char buffer[100];	/* buffer for lookupunit answers with
54717012381Scgd 				   prefixes */
54817012381Scgd 
5491ed141bbSjoerg static const char *
lookupunit(const char * unit)55095f5c18eSlukem lookupunit(const char *unit)
55117012381Scgd {
55217012381Scgd 	int i;
55317012381Scgd 	char *copy;
55417012381Scgd 
55517012381Scgd 	for (i = 0; i < unitcount; i++) {
55617012381Scgd 		if (!strcmp(unittable[i].uname, unit))
55717012381Scgd 			return unittable[i].uval;
55817012381Scgd 	}
55917012381Scgd 
56017012381Scgd 	if (unit[strlen(unit) - 1] == '^') {
56117012381Scgd 		copy = dupstr(unit);
56217012381Scgd 		copy[strlen(copy) - 1] = 0;
56317012381Scgd 		for (i = 0; i < unitcount; i++) {
56417012381Scgd 			if (!strcmp(unittable[i].uname, copy)) {
5654005e1b9Sitojun 				strlcpy(buffer, copy, sizeof(buffer));
56617012381Scgd 				free(copy);
56717012381Scgd 				return buffer;
56817012381Scgd 			}
56917012381Scgd 		}
57017012381Scgd 		free(copy);
57117012381Scgd 	}
57217012381Scgd 	if (unit[strlen(unit) - 1] == 's') {
57317012381Scgd 		copy = dupstr(unit);
57417012381Scgd 		copy[strlen(copy) - 1] = 0;
57517012381Scgd 		for (i = 0; i < unitcount; i++) {
57617012381Scgd 			if (!strcmp(unittable[i].uname, copy)) {
5774005e1b9Sitojun 				strlcpy(buffer, copy, sizeof(buffer));
57817012381Scgd 				free(copy);
57917012381Scgd 				return buffer;
58017012381Scgd 			}
58117012381Scgd 		}
58217012381Scgd 		if (copy[strlen(copy) - 1] == 'e') {
58317012381Scgd 			copy[strlen(copy) - 1] = 0;
58417012381Scgd 			for (i = 0; i < unitcount; i++) {
58517012381Scgd 				if (!strcmp(unittable[i].uname, copy)) {
5864005e1b9Sitojun 					strlcpy(buffer, copy, sizeof(buffer));
58717012381Scgd 					free(copy);
58817012381Scgd 					return buffer;
58917012381Scgd 				}
59017012381Scgd 			}
59117012381Scgd 		}
59217012381Scgd 		free(copy);
59317012381Scgd 	}
59417012381Scgd 	for (i = 0; i < prefixcount; i++) {
59517012381Scgd 		if (!strncmp(prefixtable[i].prefixname, unit,
59617012381Scgd 			strlen(prefixtable[i].prefixname))) {
59717012381Scgd 			unit += strlen(prefixtable[i].prefixname);
59817012381Scgd 			if (!strlen(unit) || lookupunit(unit)) {
5994005e1b9Sitojun 				strlcpy(buffer, prefixtable[i].prefixval,
6004005e1b9Sitojun 				    sizeof(buffer));
6014005e1b9Sitojun 				strlcat(buffer, " ", sizeof(buffer));
6024005e1b9Sitojun 				strlcat(buffer, unit, sizeof(buffer));
60317012381Scgd 				return buffer;
60417012381Scgd 			}
60517012381Scgd 		}
60617012381Scgd 	}
60717012381Scgd 	return 0;
60817012381Scgd }
60917012381Scgd 
61017012381Scgd 
61117012381Scgd 
61217012381Scgd /*
61317012381Scgd    reduces a product of symbolic units to primitive units.
61417012381Scgd    The three low bits are used to return flags:
61517012381Scgd 
61617012381Scgd      bit 0 (1) set on if reductions were performed without error.
61717012381Scgd      bit 1 (2) set on if no reductions are performed.
61817012381Scgd      bit 2 (4) set on if an unknown unit is discovered.
61917012381Scgd */
62017012381Scgd 
62117012381Scgd 
62217012381Scgd #define ERROR 4
62317012381Scgd 
6241ed141bbSjoerg static int
reduceproduct(struct unittype * theunit,int flip)62517012381Scgd reduceproduct(struct unittype * theunit, int flip)
62617012381Scgd {
62717012381Scgd 
62895f5c18eSlukem 	const char *toadd;
62995f5c18eSlukem 	const char **product;
63017012381Scgd 	int didsomething = 2;
63117012381Scgd 
63217012381Scgd 	if (flip)
63317012381Scgd 		product = theunit->denominator;
63417012381Scgd 	else
63517012381Scgd 		product = theunit->numerator;
63617012381Scgd 
63717012381Scgd 	for (; *product; product++) {
63817012381Scgd 
63917012381Scgd 		for (;;) {
64017012381Scgd 			if (!strlen(*product))
64117012381Scgd 				break;
64217012381Scgd 			toadd = lookupunit(*product);
64317012381Scgd 			if (!toadd) {
644c6933fcaSapb 				mywarnx("Unknown unit '%s'", *product);
64517012381Scgd 				return ERROR;
64617012381Scgd 			}
64717012381Scgd 			if (strchr(toadd, PRIMITIVECHAR))
64817012381Scgd 				break;
64917012381Scgd 			didsomething = 1;
65017012381Scgd 			if (*product != NULLUNIT) {
65195f5c18eSlukem 				free(__UNCONST(*product));
65217012381Scgd 				*product = NULLUNIT;
65317012381Scgd 			}
65417012381Scgd 			if (addunit(theunit, toadd, flip))
65517012381Scgd 				return ERROR;
65617012381Scgd 		}
65717012381Scgd 	}
65817012381Scgd 	return didsomething;
65917012381Scgd }
66017012381Scgd 
66117012381Scgd 
66217012381Scgd /*
66317012381Scgd    Reduces numerator and denominator of the specified unit.
66417012381Scgd    Returns 0 on success, or 1 on unknown unit error.
66517012381Scgd */
66617012381Scgd 
6671ed141bbSjoerg static int
reduceunit(struct unittype * theunit)66817012381Scgd reduceunit(struct unittype * theunit)
66917012381Scgd {
67017012381Scgd 	int ret;
67117012381Scgd 
67217012381Scgd 	ret = 1;
67317012381Scgd 	while (ret & 1) {
67417012381Scgd 		ret = reduceproduct(theunit, 0) | reduceproduct(theunit, 1);
67517012381Scgd 		if (ret & 4)
67617012381Scgd 			return 1;
67717012381Scgd 	}
67817012381Scgd 	return 0;
67917012381Scgd }
68017012381Scgd 
6811ed141bbSjoerg static int
compareproducts(const char ** one,const char ** two)68295f5c18eSlukem compareproducts(const char **one, const char **two)
68317012381Scgd {
68417012381Scgd 	while (*one || *two) {
68517012381Scgd 		if (!*one && *two != NULLUNIT)
68617012381Scgd 			return 1;
68717012381Scgd 		if (!*two && *one != NULLUNIT)
68817012381Scgd 			return 1;
68917012381Scgd 		if (*one == NULLUNIT)
69017012381Scgd 			one++;
69117012381Scgd 		else if (*two == NULLUNIT)
69217012381Scgd 			two++;
6934f0fad7eSchristos 		else if (*one && *two && strcmp(*one, *two))
69417012381Scgd 			return 1;
69517012381Scgd 		else
69617012381Scgd 			one++, two++;
69717012381Scgd 	}
69817012381Scgd 	return 0;
69917012381Scgd }
70017012381Scgd 
70117012381Scgd 
70217012381Scgd /* Return zero if units are compatible, nonzero otherwise */
70317012381Scgd 
7041ed141bbSjoerg static int
compareunits(struct unittype * first,struct unittype * second)70517012381Scgd compareunits(struct unittype * first, struct unittype * second)
70617012381Scgd {
70717012381Scgd 	return
70817012381Scgd 	compareproducts(first->numerator, second->numerator) ||
70917012381Scgd 	compareproducts(first->denominator, second->denominator);
71017012381Scgd }
71117012381Scgd 
7121ed141bbSjoerg static int
compareunitsreciprocal(struct unittype * first,struct unittype * second)7138ee74ae1Smycroft compareunitsreciprocal(struct unittype * first, struct unittype * second)
7148ee74ae1Smycroft {
7158ee74ae1Smycroft 	return
7168ee74ae1Smycroft 	compareproducts(first->numerator, second->denominator) ||
7178ee74ae1Smycroft 	compareproducts(first->denominator, second->numerator);
7188ee74ae1Smycroft }
7198ee74ae1Smycroft 
72017012381Scgd 
7211ed141bbSjoerg static int
completereduce(struct unittype * unit)72217012381Scgd completereduce(struct unittype * unit)
72317012381Scgd {
72417012381Scgd 	if (reduceunit(unit))
72517012381Scgd 		return 1;
72617012381Scgd 	sortunit(unit);
72717012381Scgd 	cancelunit(unit);
72817012381Scgd 	return 0;
72917012381Scgd }
73017012381Scgd 
73117012381Scgd 
7321ed141bbSjoerg static void
showanswer(struct unittype * have,struct unittype * want)73317012381Scgd showanswer(struct unittype * have, struct unittype * want)
73417012381Scgd {
73517012381Scgd 	if (compareunits(have, want)) {
7368ee74ae1Smycroft 		if (compareunitsreciprocal(have, want)) {
73717012381Scgd 			printf("conformability error\n");
73817012381Scgd 			showunit(have);
73917012381Scgd 			showunit(want);
7408ee74ae1Smycroft 		} else {
7418ee74ae1Smycroft 			printf("\treciprocal conversion\n");
742c6933fcaSapb 			printf("\t* %.*g\n\t/ %.*g\n",
743c6933fcaSapb 			    precision, 1 / (have->factor * want->factor),
744c6933fcaSapb 			    precision, want->factor * have->factor);
7458ee74ae1Smycroft 		}
74617012381Scgd 	}
74717012381Scgd 	else
748c6933fcaSapb 		printf("\t* %.*g\n\t/ %.*g\n",
749c6933fcaSapb 		    precision, have->factor / want->factor,
750c6933fcaSapb 		    precision, want->factor / have->factor);
75117012381Scgd }
75217012381Scgd 
753c6933fcaSapb static int
listunits(int expand)754c6933fcaSapb listunits(int expand)
755c6933fcaSapb {
756c6933fcaSapb 	struct unittype theunit;
757c6933fcaSapb 	const char *thename;
758c6933fcaSapb 	const char *thedefn;
759c6933fcaSapb 	int errors = 0;
760c6933fcaSapb 	int i;
761c6933fcaSapb 	int printexpansion;
762c6933fcaSapb 
763c6933fcaSapb 	/*
764c6933fcaSapb 	 * send error and warning messages to stdout,
765c6933fcaSapb 	 * and make them look like comments.
766c6933fcaSapb 	 */
767c6933fcaSapb 	errprefix = "/ ";
768c6933fcaSapb 
769c6933fcaSapb #if 0 /* debug */
770c6933fcaSapb 	printf("/ expand=%d precision=%d unitcount=%d prefixcount=%d\n",
771c6933fcaSapb 	    expand, precision, unitcount, prefixcount);
772c6933fcaSapb #endif
773c6933fcaSapb 
774c6933fcaSapb 	/* 1. Dump all primitive units, e.g. "m !a!", "kg !b!", ... */
775c6933fcaSapb 	printf("/ Primitive units\n");
776c6933fcaSapb 	for (i = 0; i < unitcount; i++) {
777c6933fcaSapb 		thename = unittable[i].uname;
778c6933fcaSapb 		thedefn = unittable[i].uval;
779c6933fcaSapb 		if (thedefn[0] == PRIMITIVECHAR) {
780c6933fcaSapb 			printf("%s\t%s\n", thename, thedefn);
781c6933fcaSapb 		}
782c6933fcaSapb 	}
783c6933fcaSapb 
784c6933fcaSapb 	/* 2. Dump all prefixes, e.g. "yotta- 1e24", "zetta- 1e21", ... */
785c6933fcaSapb 	printf("/ Prefixes\n");
786c6933fcaSapb 	for (i = 0; i < prefixcount; i++) {
787c6933fcaSapb 		printexpansion = expand;
788c6933fcaSapb 		thename = prefixtable[i].prefixname;
789c6933fcaSapb 		thedefn = prefixtable[i].prefixval;
790c6933fcaSapb 		if (expand) {
791c6933fcaSapb 			/*
792c6933fcaSapb 			 * prefix names are sometimes identical to unit
793c6933fcaSapb 			 * names, so we have to expand thedefn instead of
794c6933fcaSapb 			 * expanding thename.
795c6933fcaSapb 			 */
796c6933fcaSapb 			initializeunit(&theunit);
797c6933fcaSapb 			if (addunit(&theunit, thedefn, 0) != 0
798c6933fcaSapb 			    || completereduce(&theunit) != 0) {
799c6933fcaSapb 				errors++;
800c6933fcaSapb 				printexpansion = 0;
801c6933fcaSapb 				mywarnx("Error in prefix '%s-'", thename);
802c6933fcaSapb 			}
803c6933fcaSapb 		}
804c6933fcaSapb 		if (printexpansion) {
805c6933fcaSapb 			printf("%s-", thename);
806c6933fcaSapb 			showunit(&theunit);
807c6933fcaSapb 		} else
808c6933fcaSapb 			printf("%s-\t%s\n", thename, thedefn);
809c6933fcaSapb 	}
810c6933fcaSapb 
811c6933fcaSapb 	/* 3. Dump all other units. */
812c6933fcaSapb 	printf("/ Other units\n");
813c6933fcaSapb 	for (i = 0; i < unitcount; i++) {
814c6933fcaSapb 		printexpansion = expand;
815c6933fcaSapb 		thename = unittable[i].uname;
816c6933fcaSapb 		thedefn = unittable[i].uval;
817c6933fcaSapb 		if (thedefn[0] == PRIMITIVECHAR)
818c6933fcaSapb 			continue;
819c6933fcaSapb 		if (expand) {
820c6933fcaSapb 			/*
821c6933fcaSapb 			 * expand thename, not thedefn, so that
822c6933fcaSapb 			 * we can catch errors in the name itself.
823c6933fcaSapb 			 * e.g. a name that contains a hyphen
82425dbb40aSapb 			 * will be interpreted as multiplication.
825c6933fcaSapb 			 */
826c6933fcaSapb 			initializeunit(&theunit);
827e5b434bcSapb 			if (addunit(&theunit, thename, 0) != 0
828c6933fcaSapb 			    || completereduce(&theunit) != 0) {
829c6933fcaSapb 				errors++;
830c6933fcaSapb 				printexpansion = 0;
831c6933fcaSapb 				mywarnx("Error in unit '%s'", thename);
832c6933fcaSapb 			}
833c6933fcaSapb 		}
834c6933fcaSapb 		if (printexpansion) {
835c6933fcaSapb 			printf("%s", thename);
836c6933fcaSapb 			showunit(&theunit);
837c6933fcaSapb 		} else
838c6933fcaSapb 			printf("%s\t%s\n", thename, thedefn);
839c6933fcaSapb 	}
840c6933fcaSapb 
841c6933fcaSapb 	if (errors)
842c6933fcaSapb 		mywarnx("Definitions with errors: %d", errors);
843c6933fcaSapb 	return (errors ? 1 : 0);
844c6933fcaSapb }
84517012381Scgd 
8461ed141bbSjoerg static void
usage(void)8471ed141bbSjoerg usage(void)
84817012381Scgd {
8492c875534Slukem 	fprintf(stderr,
8504bdac686Swiz 	    "\nunits [-Llqv] [-f filename] [[count] from-unit to-unit]\n");
85117012381Scgd 	fprintf(stderr, "\n    -f specify units file\n");
8524bdac686Swiz 	fprintf(stderr, "    -L list units in standardized base units\n");
8534bdac686Swiz 	fprintf(stderr, "    -l list units\n");
854d68a625eSatatat 	fprintf(stderr, "    -q suppress prompting (quiet)\n");
85517012381Scgd 	fprintf(stderr, "    -v print version number\n");
85617012381Scgd 	exit(3);
85717012381Scgd }
85817012381Scgd 
859cac62b2dSjtc int
main(int argc,char ** argv)86017012381Scgd main(int argc, char **argv)
86117012381Scgd {
86217012381Scgd 
86317012381Scgd 	struct unittype have, want;
86417012381Scgd 	char havestr[81], wantstr[81];
865d7869990Smark 	int optchar;
86695f5c18eSlukem 	const char *userfile = 0;
867c6933fcaSapb 	int list = 0, listexpand = 0;
86817012381Scgd 	int quiet = 0;
86917012381Scgd 
870c6933fcaSapb 	while ((optchar = getopt(argc, argv, "lLvqf:")) != -1) {
87117012381Scgd 		switch (optchar) {
872c6933fcaSapb 		case 'l':
873c6933fcaSapb 			list = 1;
874c6933fcaSapb 			break;
875c6933fcaSapb 		case 'L':
876c6933fcaSapb 			list = 1;
877c6933fcaSapb 			listexpand = 1;
878c6933fcaSapb 			precision = DBL_DIG;
879c6933fcaSapb 			break;
88017012381Scgd 		case 'f':
88117012381Scgd 			userfile = optarg;
88217012381Scgd 			break;
88317012381Scgd 		case 'q':
88417012381Scgd 			quiet = 1;
88517012381Scgd 			break;
88617012381Scgd 		case 'v':
88717012381Scgd 			fprintf(stderr, "\n  units version %s  Copyright (c) 1993 by Adrian Mariano\n",
88817012381Scgd 			    VERSION);
88917012381Scgd 			fprintf(stderr, "                    This program may be freely distributed\n");
89017012381Scgd 			usage();
89117012381Scgd 		default:
89217012381Scgd 			usage();
89317012381Scgd 			break;
89417012381Scgd 		}
89517012381Scgd 	}
89617012381Scgd 
897d68a625eSatatat 	argc -= optind;
898d68a625eSatatat 	argv += optind;
899d68a625eSatatat 
900c6933fcaSapb 	if ((argc != 3 && argc != 2 && argc != 0)
901c6933fcaSapb 	    || (list && argc != 0))
90217012381Scgd 		usage();
90317012381Scgd 
904c6933fcaSapb 	if (list)
905c6933fcaSapb 		errprefix = "/ ";	/* set this before reading the file */
906c6933fcaSapb 
90717012381Scgd 	readunits(userfile);
90817012381Scgd 
909c6933fcaSapb 	if (list)
910c6933fcaSapb 		return listunits(listexpand);
911c6933fcaSapb 
912d68a625eSatatat 	if (argc == 3) {
9134005e1b9Sitojun 		strlcpy(havestr, argv[0], sizeof(havestr));
9144005e1b9Sitojun 		strlcat(havestr, " ", sizeof(havestr));
9154005e1b9Sitojun 		strlcat(havestr, argv[1], sizeof(havestr));
916d68a625eSatatat 		argc--;
917d68a625eSatatat 		argv++;
918d68a625eSatatat 		argv[0] = havestr;
919d68a625eSatatat 	}
920d68a625eSatatat 
921d68a625eSatatat 	if (argc == 2) {
9224005e1b9Sitojun 		strlcpy(havestr, argv[0], sizeof(havestr));
9234005e1b9Sitojun 		strlcpy(wantstr, argv[1], sizeof(wantstr));
92417012381Scgd 		initializeunit(&have);
92517012381Scgd 		addunit(&have, havestr, 0);
92617012381Scgd 		completereduce(&have);
92717012381Scgd 		initializeunit(&want);
92817012381Scgd 		addunit(&want, wantstr, 0);
92917012381Scgd 		completereduce(&want);
93017012381Scgd 		showanswer(&have, &want);
93117012381Scgd 	}
93217012381Scgd 	else {
93317012381Scgd 		if (!quiet)
93417012381Scgd 			printf("%d units, %d prefixes\n\n", unitcount,
93517012381Scgd 			    prefixcount);
93617012381Scgd 		for (;;) {
93717012381Scgd 			do {
93817012381Scgd 				initializeunit(&have);
93917012381Scgd 				if (!quiet)
94017012381Scgd 					printf("You have: ");
94117012381Scgd 				if (!fgets(havestr, 80, stdin)) {
94229301c43Skristerw 					if (!quiet)
94317012381Scgd 						putchar('\n');
94417012381Scgd 					exit(0);
94517012381Scgd 				}
94617012381Scgd 			} while (addunit(&have, havestr, 0) ||
94717012381Scgd 			    completereduce(&have));
94817012381Scgd 			do {
94917012381Scgd 				initializeunit(&want);
95017012381Scgd 				if (!quiet)
95117012381Scgd 					printf("You want: ");
95217012381Scgd 				if (!fgets(wantstr, 80, stdin)) {
95317012381Scgd 					if (!quiet)
95417012381Scgd 						putchar('\n');
95517012381Scgd 					exit(0);
95617012381Scgd 				}
95717012381Scgd 			} while (addunit(&want, wantstr, 0) ||
95817012381Scgd 			    completereduce(&want));
95917012381Scgd 			showanswer(&have, &want);
96017012381Scgd 		}
96117012381Scgd 	}
9622c875534Slukem 	return (0);
96317012381Scgd }
964