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