/* $NetBSD: units.c,v 1.27 2016/02/05 03:32:49 dholland Exp $ */ /* * units.c Copyright (c) 1993 by Adrian Mariano (adrian@cam.cornell.edu) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * Disclaimer: This software is provided by the author "as is". The author * shall not be liable for any damages caused in any way by this software. * * I would appreciate (though I do not require) receiving a copy of any * improvements you might make to this program. */ #include #include #include #include #include #include #include #include #include "pathnames.h" #define VERSION "1.0" #ifndef UNITSFILE #define UNITSFILE _PATH_UNITSLIB #endif #define MAXUNITS 1000 #define MAXPREFIXES 50 #define MAXSUBUNITS 500 #define PRIMITIVECHAR '!' static int precision = 8; /* for printf with "%.*g" format */ static const char *errprefix = NULL; /* if not NULL, then prepend this * to error messages and send them to * stdout instead of stderr. */ static const char *powerstring = "^"; static struct { const char *uname; const char *uval; } unittable[MAXUNITS]; struct unittype { const char *numerator[MAXSUBUNITS]; const char *denominator[MAXSUBUNITS]; double factor; }; struct { const char *prefixname; const char *prefixval; } prefixtable[MAXPREFIXES]; static const char *NULLUNIT = ""; static int unitcount; static int prefixcount; static int addsubunit(const char *[], const char *); static int addunit(struct unittype *, const char *, int); static void cancelunit(struct unittype *); static int compare(const void *, const void *); static int compareproducts(const char **, const char **); static int compareunits(struct unittype *, struct unittype *); static int compareunitsreciprocal(struct unittype *, struct unittype *); static int completereduce(struct unittype *); static void initializeunit(struct unittype *); static void readerror(int); static void readunits(const char *); static int reduceproduct(struct unittype *, int); static int reduceunit(struct unittype *); static void showanswer(struct unittype *, struct unittype *); static void showunit(struct unittype *); static void sortunit(struct unittype *); __dead static void usage(void); static void zeroerror(void); static char *dupstr(const char *); static const char *lookupunit(const char *); static char * dupstr(const char *str) { char *ret; ret = strdup(str); if (!ret) err(3, "Memory allocation error"); return (ret); } static __printflike(1, 2) void mywarnx(const char *fmt, ...) { va_list args; va_start(args, fmt); if (errprefix) { /* warn to stdout, with errprefix prepended */ printf("%s", errprefix); vprintf(fmt, args); printf("%s", "\n"); } else { /* warn to stderr */ vwarnx(fmt, args); } va_end(args); } static void readerror(int linenum) { mywarnx("Error in units file '%s' line %d", UNITSFILE, linenum); } static void readunits(const char *userfile) { FILE *unitfile; char line[80], *lineptr; int len, linenum, i, isdup; unitcount = 0; linenum = 0; if (userfile) { unitfile = fopen(userfile, "rt"); if (!unitfile) err(1, "Unable to open units file '%s'", userfile); } else { unitfile = fopen(UNITSFILE, "rt"); if (!unitfile) { char *direc, *env; char filename[1000]; char separator[2]; env = getenv("PATH"); if (env) { if (strchr(env, ';')) strlcpy(separator, ";", sizeof(separator)); else strlcpy(separator, ":", sizeof(separator)); direc = strtok(env, separator); while (direc) { strlcpy(filename, "", sizeof(filename)); strlcat(filename, direc, sizeof(filename)); strlcat(filename, "/", sizeof(filename)); strlcat(filename, UNITSFILE, sizeof(filename)); unitfile = fopen(filename, "rt"); if (unitfile) break; direc = strtok(NULL, separator); } } if (!unitfile) errx(1, "Can't find units file '%s'", UNITSFILE); } } while (!feof(unitfile)) { if (!fgets(line, 79, unitfile)) break; linenum++; lineptr = line; if (*lineptr == '/') continue; lineptr += strspn(lineptr, " \n\t"); len = strcspn(lineptr, " \n\t"); lineptr[len] = 0; if (!strlen(lineptr)) continue; if (lineptr[strlen(lineptr) - 1] == '-') { /* it's a prefix */ if (prefixcount == MAXPREFIXES) { mywarnx( "Memory for prefixes exceeded in line %d", linenum); continue; } lineptr[strlen(lineptr) - 1] = 0; for (isdup = 0, i = 0; i < prefixcount; i++) { if (!strcmp(prefixtable[i].prefixname, lineptr)) { isdup = 1; break; } } if (isdup) { mywarnx( "Redefinition of prefix '%s' on line %d ignored", lineptr, linenum); continue; } prefixtable[prefixcount].prefixname = dupstr(lineptr); lineptr += len + 1; if (!strlen(lineptr)) { readerror(linenum); continue; } lineptr += strspn(lineptr, " \n\t"); len = strcspn(lineptr, "\n\t"); lineptr[len] = 0; prefixtable[prefixcount++].prefixval = dupstr(lineptr); } else { /* it's not a prefix */ if (unitcount == MAXUNITS) { mywarnx("Memory for units exceeded in line %d", linenum); continue; } for (isdup = 0, i = 0; i < unitcount; i++) { if (!strcmp(unittable[i].uname, lineptr)) { isdup = 1; break; } } if (isdup) { mywarnx( "Redefinition of unit '%s' on line %d ignored", lineptr, linenum); continue; } unittable[unitcount].uname = dupstr(lineptr); lineptr += len + 1; lineptr += strspn(lineptr, " \n\t"); if (!strlen(lineptr)) { readerror(linenum); continue; } len = strcspn(lineptr, "\n\t"); lineptr[len] = 0; unittable[unitcount++].uval = dupstr(lineptr); } } fclose(unitfile); } static void initializeunit(struct unittype * theunit) { theunit->factor = 1.0; theunit->numerator[0] = theunit->denominator[0] = NULL; } static int addsubunit(const char *product[], const char *toadd) { const char **ptr; for (ptr = product; *ptr && *ptr != NULLUNIT; ptr++); if (ptr >= product + MAXSUBUNITS) { mywarnx("Memory overflow in unit reduction"); return 1; } if (!*ptr) *(ptr + 1) = 0; *ptr = dupstr(toadd); return 0; } static void showunit(struct unittype * theunit) { const char **ptr; int printedslash; int counter = 1; printf("\t%.*g", precision, theunit->factor); for (ptr = theunit->numerator; *ptr; ptr++) { if (ptr > theunit->numerator && **ptr && !strcmp(*ptr, *(ptr - 1))) counter++; else { if (counter > 1) printf("%s%d", powerstring, counter); if (**ptr) printf(" %s", *ptr); counter = 1; } } if (counter > 1) printf("%s%d", powerstring, counter); counter = 1; printedslash = 0; for (ptr = theunit->denominator; *ptr; ptr++) { if (ptr > theunit->denominator && **ptr && !strcmp(*ptr, *(ptr - 1))) counter++; else { if (counter > 1) printf("%s%d", powerstring, counter); if (**ptr) { if (!printedslash) printf(" /"); printedslash = 1; printf(" %s", *ptr); } counter = 1; } } if (counter > 1) printf("%s%d", powerstring, counter); printf("\n"); } static void zeroerror(void) { mywarnx("Unit reduces to zero"); } /* Adds the specified string to the unit. Flip is 0 for adding normally, 1 for adding reciprocal. Returns 0 for successful addition, nonzero on error. */ static int addunit(struct unittype * theunit, const char *toadd, int flip) { char *scratch, *savescr; char *item; char *divider, *slash; char *minus; size_t pos, len; int doingtop; savescr = scratch = dupstr(toadd); /* * "foot-pound" is the same as "foot pound". But don't * trash minus signs on numbers. * * 20160204 dholland: this used to let through only minus * signs at the beginning of the string or in the middle of a * floating constant (e.g. 3.6e-5), and a minus sign at the * beginning of the string failed further on. I have changed * it so any minus sign before a digit (or decimal point) is * treated as going with that digit. * * Note that this changed the interpretation of certain * marginally valid inputs like "3 N-5 s"; that used to be * interpreted as "3 N 5 s" or 15 N s, but now it reads as * "3 N -5 s" or -15 N s. However, it also makes negative * exponents on units work, which used to be silently trashed. */ for (minus = scratch + 1; *minus; minus++) { if (*minus != '-') { continue; } if (strchr(".0123456789", *(minus + 1))) { continue; } *minus = ' '; } /* Process up to the next / in one go. */ slash = strchr(scratch, '/'); if (slash) *slash = 0; doingtop = 1; do { item = strtok(scratch, " *\t\n/"); while (item) { if ((*item == '-' && strchr("0123456789.", *(item+1))) || strchr("0123456789.", *item)) { /* item starts with a number */ char *endptr; double num; divider = strchr(item, '|'); if (divider) { *divider = 0; num = strtod(item, &endptr); if (!num) { zeroerror(); return 1; } if (endptr != divider) { /* "6foo|2" is an error */ mywarnx("Junk before '|'"); return 1; } if (doingtop ^ flip) theunit->factor *= num; else theunit->factor /= num; num = strtod(divider + 1, &endptr); if (!num) { zeroerror(); return 1; } if (doingtop ^ flip) theunit->factor /= num; else theunit->factor *= num; if (*endptr) { /* "6|2foo" is like "6|2 foo" */ item = endptr; continue; } } else { num = strtod(item, &endptr); if (!num) { zeroerror(); return 1; } if (doingtop ^ flip) theunit->factor *= num; else theunit->factor /= num; if (*endptr) { /* "3foo" is like "3 foo" */ item = endptr; continue; } } } else { /* item is not a number */ int repeat = 1; int flipthis = 0; pos = len = strlen(item); assert(pos > 0); while (strchr("0123456789", item[pos - 1])) { pos--; /* string began with non-digit */ assert(pos > 0); } if (pos < len) { if (pos > 1 && item[pos - 1] == '-' && item[pos - 2] == '^') { /* allow negative exponents */ pos--; } /* have an exponent */ repeat = strtol(item + pos, NULL, 10); item[pos] = 0; if (repeat == 0) { /* not really the right msg */ zeroerror(); return 1; } if (repeat < 0) { flipthis = 1; repeat = -repeat; } } flipthis ^= doingtop ^ flip; for (; repeat; repeat--) if (addsubunit(flipthis ? theunit->numerator : theunit->denominator, item)) return 1; } item = strtok(NULL, " *\t/\n"); } doingtop--; if (slash) { scratch = slash + 1; } else doingtop--; } while (doingtop >= 0); free(savescr); return 0; } static int compare(const void *item1, const void *item2) { return strcmp(*(const char * const *) item1, *(const char * const *) item2); } static void sortunit(struct unittype * theunit) { const char **ptr; int count; for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++); qsort(theunit->numerator, count, sizeof(char *), compare); for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++); qsort(theunit->denominator, count, sizeof(char *), compare); } static void cancelunit(struct unittype * theunit) { const char **den, **num; int comp; den = theunit->denominator; num = theunit->numerator; while (*num && *den) { comp = strcmp(*den, *num); if (!comp) { /* if (*den!=NULLUNIT) free(*den); if (*num!=NULLUNIT) free(*num);*/ *den++ = NULLUNIT; *num++ = NULLUNIT; } else if (comp < 0) den++; else num++; } } /* Looks up the definition for the specified unit. Returns a pointer to the definition or a null pointer if the specified unit does not appear in the units table. */ static char buffer[100]; /* buffer for lookupunit answers with prefixes */ static const char * lookupunit(const char *unit) { int i; char *copy; for (i = 0; i < unitcount; i++) { if (!strcmp(unittable[i].uname, unit)) return unittable[i].uval; } if (unit[strlen(unit) - 1] == '^') { copy = dupstr(unit); copy[strlen(copy) - 1] = 0; for (i = 0; i < unitcount; i++) { if (!strcmp(unittable[i].uname, copy)) { strlcpy(buffer, copy, sizeof(buffer)); free(copy); return buffer; } } free(copy); } if (unit[strlen(unit) - 1] == 's') { copy = dupstr(unit); copy[strlen(copy) - 1] = 0; for (i = 0; i < unitcount; i++) { if (!strcmp(unittable[i].uname, copy)) { strlcpy(buffer, copy, sizeof(buffer)); free(copy); return buffer; } } if (copy[strlen(copy) - 1] == 'e') { copy[strlen(copy) - 1] = 0; for (i = 0; i < unitcount; i++) { if (!strcmp(unittable[i].uname, copy)) { strlcpy(buffer, copy, sizeof(buffer)); free(copy); return buffer; } } } free(copy); } for (i = 0; i < prefixcount; i++) { if (!strncmp(prefixtable[i].prefixname, unit, strlen(prefixtable[i].prefixname))) { unit += strlen(prefixtable[i].prefixname); if (!strlen(unit) || lookupunit(unit)) { strlcpy(buffer, prefixtable[i].prefixval, sizeof(buffer)); strlcat(buffer, " ", sizeof(buffer)); strlcat(buffer, unit, sizeof(buffer)); return buffer; } } } return 0; } /* reduces a product of symbolic units to primitive units. The three low bits are used to return flags: bit 0 (1) set on if reductions were performed without error. bit 1 (2) set on if no reductions are performed. bit 2 (4) set on if an unknown unit is discovered. */ #define ERROR 4 static int reduceproduct(struct unittype * theunit, int flip) { const char *toadd; const char **product; int didsomething = 2; if (flip) product = theunit->denominator; else product = theunit->numerator; for (; *product; product++) { for (;;) { if (!strlen(*product)) break; toadd = lookupunit(*product); if (!toadd) { mywarnx("Unknown unit '%s'", *product); return ERROR; } if (strchr(toadd, PRIMITIVECHAR)) break; didsomething = 1; if (*product != NULLUNIT) { free(__UNCONST(*product)); *product = NULLUNIT; } if (addunit(theunit, toadd, flip)) return ERROR; } } return didsomething; } /* Reduces numerator and denominator of the specified unit. Returns 0 on success, or 1 on unknown unit error. */ static int reduceunit(struct unittype * theunit) { int ret; ret = 1; while (ret & 1) { ret = reduceproduct(theunit, 0) | reduceproduct(theunit, 1); if (ret & 4) return 1; } return 0; } static int compareproducts(const char **one, const char **two) { while (*one || *two) { if (!*one && *two != NULLUNIT) return 1; if (!*two && *one != NULLUNIT) return 1; if (*one == NULLUNIT) one++; else if (*two == NULLUNIT) two++; else if (*one && *two && strcmp(*one, *two)) return 1; else one++, two++; } return 0; } /* Return zero if units are compatible, nonzero otherwise */ static int compareunits(struct unittype * first, struct unittype * second) { return compareproducts(first->numerator, second->numerator) || compareproducts(first->denominator, second->denominator); } static int compareunitsreciprocal(struct unittype * first, struct unittype * second) { return compareproducts(first->numerator, second->denominator) || compareproducts(first->denominator, second->numerator); } static int completereduce(struct unittype * unit) { if (reduceunit(unit)) return 1; sortunit(unit); cancelunit(unit); return 0; } static void showanswer(struct unittype * have, struct unittype * want) { if (compareunits(have, want)) { if (compareunitsreciprocal(have, want)) { printf("conformability error\n"); showunit(have); showunit(want); } else { printf("\treciprocal conversion\n"); printf("\t* %.*g\n\t/ %.*g\n", precision, 1 / (have->factor * want->factor), precision, want->factor * have->factor); } } else printf("\t* %.*g\n\t/ %.*g\n", precision, have->factor / want->factor, precision, want->factor / have->factor); } static int listunits(int expand) { struct unittype theunit; const char *thename; const char *thedefn; int errors = 0; int i; int printexpansion; /* * send error and warning messages to stdout, * and make them look like comments. */ errprefix = "/ "; #if 0 /* debug */ printf("/ expand=%d precision=%d unitcount=%d prefixcount=%d\n", expand, precision, unitcount, prefixcount); #endif /* 1. Dump all primitive units, e.g. "m !a!", "kg !b!", ... */ printf("/ Primitive units\n"); for (i = 0; i < unitcount; i++) { thename = unittable[i].uname; thedefn = unittable[i].uval; if (thedefn[0] == PRIMITIVECHAR) { printf("%s\t%s\n", thename, thedefn); } } /* 2. Dump all prefixes, e.g. "yotta- 1e24", "zetta- 1e21", ... */ printf("/ Prefixes\n"); for (i = 0; i < prefixcount; i++) { printexpansion = expand; thename = prefixtable[i].prefixname; thedefn = prefixtable[i].prefixval; if (expand) { /* * prefix names are sometimes identical to unit * names, so we have to expand thedefn instead of * expanding thename. */ initializeunit(&theunit); if (addunit(&theunit, thedefn, 0) != 0 || completereduce(&theunit) != 0) { errors++; printexpansion = 0; mywarnx("Error in prefix '%s-'", thename); } } if (printexpansion) { printf("%s-", thename); showunit(&theunit); } else printf("%s-\t%s\n", thename, thedefn); } /* 3. Dump all other units. */ printf("/ Other units\n"); for (i = 0; i < unitcount; i++) { printexpansion = expand; thename = unittable[i].uname; thedefn = unittable[i].uval; if (thedefn[0] == PRIMITIVECHAR) continue; if (expand) { /* * expand thename, not thedefn, so that * we can catch errors in the name itself. * e.g. a name that contains a hyphen * will be interpreted as multiplication. */ initializeunit(&theunit); if (addunit(&theunit, thename, 0) != 0 || completereduce(&theunit) != 0) { errors++; printexpansion = 0; mywarnx("Error in unit '%s'", thename); } } if (printexpansion) { printf("%s", thename); showunit(&theunit); } else printf("%s\t%s\n", thename, thedefn); } if (errors) mywarnx("Definitions with errors: %d", errors); return (errors ? 1 : 0); } static void usage(void) { fprintf(stderr, "\nunits [-Llqv] [-f filename] [[count] from-unit to-unit]\n"); fprintf(stderr, "\n -f specify units file\n"); fprintf(stderr, " -L list units in standardized base units\n"); fprintf(stderr, " -l list units\n"); fprintf(stderr, " -q suppress prompting (quiet)\n"); fprintf(stderr, " -v print version number\n"); exit(3); } int main(int argc, char **argv) { struct unittype have, want; char havestr[81], wantstr[81]; int optchar; const char *userfile = 0; int list = 0, listexpand = 0; int quiet = 0; while ((optchar = getopt(argc, argv, "lLvqf:")) != -1) { switch (optchar) { case 'l': list = 1; break; case 'L': list = 1; listexpand = 1; precision = DBL_DIG; break; case 'f': userfile = optarg; break; case 'q': quiet = 1; break; case 'v': fprintf(stderr, "\n units version %s Copyright (c) 1993 by Adrian Mariano\n", VERSION); fprintf(stderr, " This program may be freely distributed\n"); usage(); default: usage(); break; } } argc -= optind; argv += optind; if ((argc != 3 && argc != 2 && argc != 0) || (list && argc != 0)) usage(); if (list) errprefix = "/ "; /* set this before reading the file */ readunits(userfile); if (list) return listunits(listexpand); if (argc == 3) { strlcpy(havestr, argv[0], sizeof(havestr)); strlcat(havestr, " ", sizeof(havestr)); strlcat(havestr, argv[1], sizeof(havestr)); argc--; argv++; argv[0] = havestr; } if (argc == 2) { strlcpy(havestr, argv[0], sizeof(havestr)); strlcpy(wantstr, argv[1], sizeof(wantstr)); initializeunit(&have); addunit(&have, havestr, 0); completereduce(&have); initializeunit(&want); addunit(&want, wantstr, 0); completereduce(&want); showanswer(&have, &want); } else { if (!quiet) printf("%d units, %d prefixes\n\n", unitcount, prefixcount); for (;;) { do { initializeunit(&have); if (!quiet) printf("You have: "); if (!fgets(havestr, 80, stdin)) { if (!quiet) putchar('\n'); exit(0); } } while (addunit(&have, havestr, 0) || completereduce(&have)); do { initializeunit(&want); if (!quiet) printf("You want: "); if (!fgets(wantstr, 80, stdin)) { if (!quiet) putchar('\n'); exit(0); } } while (addunit(&want, wantstr, 0) || completereduce(&want)); showanswer(&have, &want); } } return (0); }