diff options
Diffstat (limited to 'usr.bin/units/units.c')
-rw-r--r-- | usr.bin/units/units.c | 886 |
1 files changed, 886 insertions, 0 deletions
diff --git a/usr.bin/units/units.c b/usr.bin/units/units.c new file mode 100644 index 000000000000..1b6d0ed90b4a --- /dev/null +++ b/usr.bin/units/units.c @@ -0,0 +1,886 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * 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 <ctype.h> +#include <err.h> +#include <errno.h> +#include <histedit.h> +#include <getopt.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <capsicum_helpers.h> + +#ifndef UNITSFILE +#define UNITSFILE "/usr/share/misc/definitions.units" +#endif + +#define MAXUNITS 1000 +#define MAXPREFIXES 100 + +#define MAXSUBUNITS 500 + +#define PRIMITIVECHAR '!' + +static const char *powerstring = "^"; +static const char *numfmt = "%.8g"; + +static struct { + char *uname; + char *uval; +} unittable[MAXUNITS]; + +struct unittype { + char *numerator[MAXSUBUNITS]; + char *denominator[MAXSUBUNITS]; + double factor; + double offset; + int quantity; +}; + +static struct { + char *prefixname; + char *prefixval; +} prefixtable[MAXPREFIXES]; + + +static char NULLUNIT[] = ""; + +#define SEPARATOR ":" + +static int unitcount; +static int prefixcount; +static bool verbose = false; +static bool terse = false; +static const char * outputformat; +static const char * havestr; +static const char * wantstr; + +static int addsubunit(char *product[], char *toadd); +static int addunit(struct unittype *theunit, const char *toadd, int flip, int quantity); +static void cancelunit(struct unittype * theunit); +static int compare(const void *item1, const void *item2); +static int compareproducts(char **one, char **two); +static int compareunits(struct unittype * first, struct unittype * second); +static int completereduce(struct unittype * unit); +static char *dupstr(const char *str); +static void initializeunit(struct unittype * theunit); +static char *lookupunit(const char *unit); +static void readunits(const char *userfile); +static int reduceproduct(struct unittype * theunit, int flip); +static int reduceunit(struct unittype * theunit); +static void showanswer(struct unittype * have, struct unittype * want); +static void showunit(struct unittype * theunit); +static void sortunit(struct unittype * theunit); +static void usage(void); +static void zeroerror(void); + +static const char* promptstr = ""; + +static const char * prompt(EditLine *e __unused) { + return promptstr; +} + +static char * +dupstr(const char *str) +{ + char *ret; + + ret = strdup(str); + if (!ret) + err(3, "dupstr"); + return (ret); +} + + +static void +readunits(const char *userfile) +{ + FILE *unitfile; + char line[512], *lineptr; + int len, linenum, i; + cap_rights_t unitfilerights; + + unitcount = 0; + linenum = 0; + + if (userfile) { + unitfile = fopen(userfile, "r"); + if (!unitfile) + errx(1, "unable to open units file '%s'", userfile); + } + else { + unitfile = fopen(UNITSFILE, "r"); + if (!unitfile) { + char *direc, *env; + char filename[1000]; + + env = getenv("PATH"); + if (env) { + direc = strtok(env, SEPARATOR); + while (direc) { + snprintf(filename, sizeof(filename), + "%s/%s", direc, UNITSFILE); + unitfile = fopen(filename, "rt"); + if (unitfile) + break; + direc = strtok(NULL, SEPARATOR); + } + } + if (!unitfile) + errx(1, "can't find units file '%s'", UNITSFILE); + } + } + cap_rights_init(&unitfilerights, CAP_READ, CAP_FSTAT); + if (caph_rights_limit(fileno(unitfile), &unitfilerights) < 0) + err(1, "cap_rights_limit() failed"); + while (!feof(unitfile)) { + if (!fgets(line, sizeof(line), unitfile)) + break; + linenum++; + lineptr = line; + if (*lineptr == '/' || *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) { + warnx("memory for prefixes exceeded in line %d", linenum); + continue; + } + lineptr[strlen(lineptr) - 1] = 0; + prefixtable[prefixcount].prefixname = dupstr(lineptr); + for (i = 0; i < prefixcount; i++) + if (!strcmp(prefixtable[i].prefixname, lineptr)) { + warnx("redefinition of prefix '%s' on line %d ignored", + lineptr, linenum); + continue; + } + lineptr += len + 1; + lineptr += strspn(lineptr, " \n\t"); + len = strcspn(lineptr, "\n\t"); + if (len == 0) { + warnx("unexpected end of prefix on line %d", + linenum); + continue; + } + lineptr[len] = 0; + prefixtable[prefixcount++].prefixval = dupstr(lineptr); + } + else { /* it's not a prefix */ + if (unitcount == MAXUNITS) { + warnx("memory for units exceeded in line %d", linenum); + continue; + } + unittable[unitcount].uname = dupstr(lineptr); + for (i = 0; i < unitcount; i++) + if (!strcmp(unittable[i].uname, lineptr)) { + warnx("redefinition of unit '%s' on line %d ignored", + lineptr, linenum); + continue; + } + lineptr += len + 1; + lineptr += strspn(lineptr, " \n\t"); + if (!strlen(lineptr)) { + warnx("unexpected end of unit on line %d", + linenum); + continue; + } + len = strcspn(lineptr, "\n\t"); + lineptr[len] = 0; + unittable[unitcount++].uval = dupstr(lineptr); + } + } + fclose(unitfile); +} + +static void +initializeunit(struct unittype * theunit) +{ + theunit->numerator[0] = theunit->denominator[0] = NULL; + theunit->factor = 1.0; + theunit->offset = 0.0; + theunit->quantity = 0; +} + + +static int +addsubunit(char *product[], char *toadd) +{ + char **ptr; + + for (ptr = product; *ptr && *ptr != NULLUNIT; ptr++); + if (ptr >= product + MAXSUBUNITS) { + warnx("memory overflow in unit reduction"); + return 1; + } + if (!*ptr) + *(ptr + 1) = NULL; + *ptr = dupstr(toadd); + return 0; +} + + +static void +showunit(struct unittype * theunit) +{ + char **ptr; + int printedslash; + int counter = 1; + + printf(numfmt, theunit->factor); + if (theunit->offset) + printf("&%.8g", theunit->offset); + 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"); +} + + +void +zeroerror(void) +{ + warnx("unit reduces to zero"); +} + +/* + Adds the specified string to the unit. + Flip is 0 for adding normally, 1 for adding reciprocal. + Quantity is 1 if this is a quantity to be converted rather than a pure unit. + + Returns 0 for successful addition, nonzero on error. +*/ + +static int +addunit(struct unittype * theunit, const char *toadd, int flip, int quantity) +{ + char *scratch, *savescr; + char *item; + char *divider, *slash, *offset; + int doingtop; + + if (!strlen(toadd)) + return 1; + + savescr = scratch = dupstr(toadd); + for (slash = scratch + 1; *slash; slash++) + if (*slash == '-' && + (tolower(*(slash - 1)) != 'e' || + !strchr(".0123456789", *(slash + 1)))) + *slash = ' '; + slash = strchr(scratch, '/'); + if (slash) + *slash = 0; + doingtop = 1; + do { + item = strtok(scratch, " *\t\n/"); + while (item) { + if (strchr("0123456789.", *item)) { /* item is a number */ + double num, offsetnum; + + if (quantity) + theunit->quantity = 1; + + offset = strchr(item, '&'); + if (offset) { + *offset = 0; + offsetnum = atof(offset+1); + } else + offsetnum = 0.0; + + divider = strchr(item, '|'); + if (divider) { + *divider = 0; + num = atof(item); + if (!num) { + zeroerror(); + free(savescr); + return 1; + } + if (doingtop ^ flip) { + theunit->factor *= num; + theunit->offset *= num; + } else { + theunit->factor /= num; + theunit->offset /= num; + } + num = atof(divider + 1); + if (!num) { + zeroerror(); + free(savescr); + return 1; + } + if (doingtop ^ flip) { + theunit->factor /= num; + theunit->offset /= num; + } else { + theunit->factor *= num; + theunit->offset *= num; + } + } + else { + num = atof(item); + if (!num) { + zeroerror(); + free(savescr); + return 1; + } + if (doingtop ^ flip) { + theunit->factor *= num; + theunit->offset *= num; + } else { + theunit->factor /= num; + theunit->offset /= num; + } + } + if (doingtop ^ flip) + theunit->offset += offsetnum; + } + else { /* item is not a number */ + int repeat = 1; + + if (strchr("23456789", + item[strlen(item) - 1])) { + repeat = item[strlen(item) - 1] - '0'; + item[strlen(item) - 1] = 0; + } + for (; repeat; repeat--) { + if (addsubunit(doingtop ^ flip ? theunit->numerator : theunit->denominator, item)) { + free(savescr); + 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) +{ + char **ptr; + unsigned 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); +} + + +void +cancelunit(struct unittype * theunit) +{ + 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 */ + +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); + } else 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++) { + size_t len = strlen(prefixtable[i].prefixname); + if (!strncmp(prefixtable[i].prefixname, unit, len)) { + if (!strlen(unit + len) || lookupunit(unit + len)) { + snprintf(buffer, sizeof(buffer), "%s %s", + prefixtable[i].prefixval, unit + len); + 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) +{ + + char *toadd; + 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) { + printf("unknown unit '%s'\n", *product); + return ERROR; + } + if (strchr(toadd, PRIMITIVECHAR)) + break; + didsomething = 1; + if (*product != NULLUNIT) { + free(*product); + *product = NULLUNIT; + } + if (addunit(theunit, toadd, flip, 0)) + 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(char **one, 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 (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 +completereduce(struct unittype * unit) +{ + if (reduceunit(unit)) + return 1; + sortunit(unit); + cancelunit(unit); + return 0; +} + +static void +showanswer(struct unittype * have, struct unittype * want) +{ + double ans; + char* oformat; + + if (compareunits(have, want)) { + printf("conformability error\n"); + if (verbose) + printf("\t%s = ", havestr); + else if (!terse) + printf("\t"); + showunit(have); + if (!terse) { + if (verbose) + printf("\t%s = ", wantstr); + else + printf("\t"); + showunit(want); + } + } + else if (have->offset != want->offset) { + if (want->quantity) + printf("WARNING: conversion of non-proportional quantities.\n"); + if (have->quantity) { + asprintf(&oformat, "\t%s\n", outputformat); + printf(oformat, + (have->factor + have->offset-want->offset)/want->factor); + free(oformat); + } + else { + asprintf(&oformat, "\t (-> x*%sg %sg)\n\t (<- y*%sg %sg)\n", + outputformat, outputformat, outputformat, outputformat); + printf(oformat, + have->factor / want->factor, + (have->offset-want->offset)/want->factor, + want->factor / have->factor, + (want->offset - have->offset)/have->factor); + } + } + else { + ans = have->factor / want->factor; + + if (verbose) { + printf("\t%s = ", havestr); + printf(outputformat, ans); + printf(" * %s", wantstr); + printf("\n"); + } + else if (terse) { + printf(outputformat, ans); + printf("\n"); + } + else { + printf("\t* "); + printf(outputformat, ans); + printf("\n"); + } + + if (verbose) { + printf("\t%s = (1 / ", havestr); + printf(outputformat, 1/ans); + printf(") * %s\n", wantstr); + } + else if (!terse) { + printf("\t/ "); + printf(outputformat, 1/ans); + printf("\n"); + } + } +} + + +static void __dead2 +usage(void) +{ + fprintf(stderr, + "usage: units [-ehqtUVv] [-f unitsfile] [-o format] [from to]\n"); + exit(3); +} + +static struct option longopts[] = { + {"exponential", no_argument, NULL, 'e'}, + {"file", required_argument, NULL, 'f'}, + {"history", required_argument, NULL, 'H'}, + {"help", no_argument, NULL, 'h'}, + {"output-format", required_argument, NULL, 'o'}, + {"quiet", no_argument, NULL, 'q'}, + {"terse", no_argument, NULL, 't'}, + {"unitsfile", no_argument, NULL, 'U'}, + {"version", no_argument, NULL, 'V'}, + {"verbose", no_argument, NULL, 'v'}, + { 0, 0, 0, 0 } +}; + + +int +main(int argc, char **argv) +{ + + struct unittype have, want; + int optchar; + bool quiet; + bool readfile; + bool quit; + History *inhistory; + EditLine *el; + HistEvent ev; + int inputsz; + + quiet = false; + readfile = false; + outputformat = numfmt; + quit = false; + while ((optchar = getopt_long(argc, argv, "+ehf:o:qtvH:UV", longopts, NULL)) != -1) { + switch (optchar) { + case 'e': + outputformat = "%6e"; + break; + case 'f': + readfile = true; + if (strlen(optarg) == 0) + readunits(NULL); + else + readunits(optarg); + break; + case 'H': + /* Ignored, for compatibility with GNU units. */ + break; + case 'q': + quiet = true; + break; + case 't': + terse = true; + break; + case 'o': + outputformat = optarg; + break; + case 'v': + verbose = true; + break; + case 'V': + fprintf(stderr, "FreeBSD units\n"); + /* FALLTHROUGH */ + case 'U': + if (access(UNITSFILE, F_OK) == 0) + printf("%s\n", UNITSFILE); + else + printf("Units data file not found"); + exit(0); + case 'h': + /* FALLTHROUGH */ + + default: + usage(); + } + } + + if (!readfile) + readunits(NULL); + + if (optind == argc - 2) { + if (caph_enter() < 0) + err(1, "unable to enter capability mode"); + + havestr = argv[optind]; + wantstr = argv[optind + 1]; + initializeunit(&have); + addunit(&have, havestr, 0, 1); + completereduce(&have); + initializeunit(&want); + addunit(&want, wantstr, 0, 1); + completereduce(&want); + showanswer(&have, &want); + } else { + inhistory = history_init(); + el = el_init(argv[0], stdin, stdout, stderr); + el_set(el, EL_PROMPT, &prompt); + el_set(el, EL_EDITOR, "emacs"); + el_set(el, EL_SIGNAL, 1); + el_set(el, EL_HIST, history, inhistory); + el_source(el, NULL); + history(inhistory, &ev, H_SETSIZE, 800); + if (inhistory == 0) + err(1, "Could not initialize history"); + + if (caph_enter() < 0) + err(1, "unable to enter capability mode"); + + if (!quiet) + printf("%d units, %d prefixes\n", unitcount, + prefixcount); + while (!quit) { + do { + initializeunit(&have); + if (!quiet) + promptstr = "You have: "; + havestr = el_gets(el, &inputsz); + if (havestr == NULL) { + quit = true; + break; + } + if (inputsz > 0) + history(inhistory, &ev, H_ENTER, + havestr); + } while (addunit(&have, havestr, 0, 1) || + completereduce(&have)); + if (quit) { + break; + } + do { + initializeunit(&want); + if (!quiet) + promptstr = "You want: "; + wantstr = el_gets(el, &inputsz); + if (wantstr == NULL) { + quit = true; + break; + } + if (inputsz > 0) + history(inhistory, &ev, H_ENTER, + wantstr); + } while (addunit(&want, wantstr, 0, 1) || + completereduce(&want)); + if (quit) { + break; + } + showanswer(&have, &want); + } + + history_end(inhistory); + el_end(el); + } + + return (0); +} |