1 /* 2 * units.c Copyright (c) 1993 by Adrian Mariano (adrian@cam.cornell.edu) 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. The name of the author may not be used to endorse or promote products 10 * derived from this software without specific prior written permission. 11 * Disclaimer: This software is provided by the author "as is". The author 12 * shall not be liable for any damages caused in any way by this software. 13 * 14 * I would appreciate (though I do not require) receiving a copy of any 15 * improvements you might make to this program. 16 */ 17 18 #ifndef lint 19 static const char rcsid[] = 20 "$FreeBSD$"; 21 #endif /* not lint */ 22 23 #include <ctype.h> 24 #include <err.h> 25 #include <errno.h> 26 #include <histedit.h> 27 #include <getopt.h> 28 #include <stdbool.h> 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <unistd.h> 33 34 #include <sys/capsicum.h> 35 36 #include "pathnames.h" 37 38 #ifndef UNITSFILE 39 #define UNITSFILE _PATH_UNITSLIB 40 #endif 41 42 #define MAXUNITS 1000 43 #define MAXPREFIXES 100 44 45 #define MAXSUBUNITS 500 46 47 #define PRIMITIVECHAR '!' 48 49 static const char *powerstring = "^"; 50 51 static struct { 52 char *uname; 53 char *uval; 54 } unittable[MAXUNITS]; 55 56 struct unittype { 57 char *numerator[MAXSUBUNITS]; 58 char *denominator[MAXSUBUNITS]; 59 double factor; 60 double offset; 61 int quantity; 62 }; 63 64 static struct { 65 char *prefixname; 66 char *prefixval; 67 } prefixtable[MAXPREFIXES]; 68 69 70 static char NULLUNIT[] = ""; 71 72 #ifdef MSDOS 73 #define SEPARATOR ";" 74 #else 75 #define SEPARATOR ":" 76 #endif 77 78 static int unitcount; 79 static int prefixcount; 80 static bool verbose = false; 81 static bool terse = false; 82 static const char * havestr; 83 static const char * wantstr; 84 85 static int addsubunit(char *product[], char *toadd); 86 static int addunit(struct unittype *theunit, const char *toadd, int flip, int quantity); 87 static void cancelunit(struct unittype * theunit); 88 static int compare(const void *item1, const void *item2); 89 static int compareproducts(char **one, char **two); 90 static int compareunits(struct unittype * first, struct unittype * second); 91 static int completereduce(struct unittype * unit); 92 static char *dupstr(const char *str); 93 static void initializeunit(struct unittype * theunit); 94 static char *lookupunit(const char *unit); 95 static void readunits(const char *userfile); 96 static int reduceproduct(struct unittype * theunit, int flip); 97 static int reduceunit(struct unittype * theunit); 98 static void showanswer(struct unittype * have, struct unittype * want); 99 static void showunit(struct unittype * theunit); 100 static void sortunit(struct unittype * theunit); 101 static void usage(void); 102 static void zeroerror(void); 103 104 static const char* promptstr = ""; 105 106 static const char * prompt(EditLine *e __unused) { 107 return promptstr; 108 } 109 110 char * 111 dupstr(const char *str) 112 { 113 char *ret; 114 115 ret = strdup(str); 116 if (!ret) 117 err(3, "dupstr"); 118 return (ret); 119 } 120 121 122 void 123 readunits(const char *userfile) 124 { 125 FILE *unitfile; 126 char line[512], *lineptr; 127 int len, linenum, i; 128 cap_rights_t unitfilerights; 129 130 unitcount = 0; 131 linenum = 0; 132 133 if (userfile) { 134 unitfile = fopen(userfile, "rt"); 135 if (!unitfile) 136 errx(1, "unable to open units file '%s'", userfile); 137 } 138 else { 139 unitfile = fopen(UNITSFILE, "rt"); 140 if (!unitfile) { 141 char *direc, *env; 142 char filename[1000]; 143 144 env = getenv("PATH"); 145 if (env) { 146 direc = strtok(env, SEPARATOR); 147 while (direc) { 148 snprintf(filename, sizeof(filename), 149 "%s/%s", direc, UNITSFILE); 150 unitfile = fopen(filename, "rt"); 151 if (unitfile) 152 break; 153 direc = strtok(NULL, SEPARATOR); 154 } 155 } 156 if (!unitfile) 157 errx(1, "can't find units file '%s'", UNITSFILE); 158 } 159 } 160 cap_rights_init(&unitfilerights, CAP_READ, CAP_FSTAT); 161 if (cap_rights_limit(fileno(unitfile), &unitfilerights) < 0 162 && errno != ENOSYS) 163 err(1, "cap_rights_limit() failed"); 164 while (!feof(unitfile)) { 165 if (!fgets(line, sizeof(line), unitfile)) 166 break; 167 linenum++; 168 lineptr = line; 169 if (*lineptr == '/') 170 continue; 171 lineptr += strspn(lineptr, " \n\t"); 172 len = strcspn(lineptr, " \n\t"); 173 lineptr[len] = 0; 174 if (!strlen(lineptr)) 175 continue; 176 if (lineptr[strlen(lineptr) - 1] == '-') { /* it's a prefix */ 177 if (prefixcount == MAXPREFIXES) { 178 warnx("memory for prefixes exceeded in line %d", linenum); 179 continue; 180 } 181 lineptr[strlen(lineptr) - 1] = 0; 182 prefixtable[prefixcount].prefixname = dupstr(lineptr); 183 for (i = 0; i < prefixcount; i++) 184 if (!strcmp(prefixtable[i].prefixname, lineptr)) { 185 warnx("redefinition of prefix '%s' on line %d ignored", 186 lineptr, linenum); 187 continue; 188 } 189 lineptr += len + 1; 190 lineptr += strspn(lineptr, " \n\t"); 191 len = strcspn(lineptr, "\n\t"); 192 if (len == 0) { 193 warnx("unexpected end of prefix on line %d", 194 linenum); 195 continue; 196 } 197 lineptr[len] = 0; 198 prefixtable[prefixcount++].prefixval = dupstr(lineptr); 199 } 200 else { /* it's not a prefix */ 201 if (unitcount == MAXUNITS) { 202 warnx("memory for units exceeded in line %d", linenum); 203 continue; 204 } 205 unittable[unitcount].uname = dupstr(lineptr); 206 for (i = 0; i < unitcount; i++) 207 if (!strcmp(unittable[i].uname, lineptr)) { 208 warnx("redefinition of unit '%s' on line %d ignored", 209 lineptr, linenum); 210 continue; 211 } 212 lineptr += len + 1; 213 lineptr += strspn(lineptr, " \n\t"); 214 if (!strlen(lineptr)) { 215 warnx("unexpected end of unit on line %d", 216 linenum); 217 continue; 218 } 219 len = strcspn(lineptr, "\n\t"); 220 lineptr[len] = 0; 221 unittable[unitcount++].uval = dupstr(lineptr); 222 } 223 } 224 fclose(unitfile); 225 } 226 227 void 228 initializeunit(struct unittype * theunit) 229 { 230 theunit->numerator[0] = theunit->denominator[0] = NULL; 231 theunit->factor = 1.0; 232 theunit->offset = 0.0; 233 theunit->quantity = 0; 234 } 235 236 237 int 238 addsubunit(char *product[], char *toadd) 239 { 240 char **ptr; 241 242 for (ptr = product; *ptr && *ptr != NULLUNIT; ptr++); 243 if (ptr >= product + MAXSUBUNITS) { 244 warnx("memory overflow in unit reduction"); 245 return 1; 246 } 247 if (!*ptr) 248 *(ptr + 1) = NULL; 249 *ptr = dupstr(toadd); 250 return 0; 251 } 252 253 254 void 255 showunit(struct unittype * theunit) 256 { 257 char **ptr; 258 int printedslash; 259 int counter = 1; 260 261 printf("%.8g", theunit->factor); 262 if (theunit->offset) 263 printf("&%.8g", theunit->offset); 264 for (ptr = theunit->numerator; *ptr; ptr++) { 265 if (ptr > theunit->numerator && **ptr && 266 !strcmp(*ptr, *(ptr - 1))) 267 counter++; 268 else { 269 if (counter > 1) 270 printf("%s%d", powerstring, counter); 271 if (**ptr) 272 printf(" %s", *ptr); 273 counter = 1; 274 } 275 } 276 if (counter > 1) 277 printf("%s%d", powerstring, counter); 278 counter = 1; 279 printedslash = 0; 280 for (ptr = theunit->denominator; *ptr; ptr++) { 281 if (ptr > theunit->denominator && **ptr && 282 !strcmp(*ptr, *(ptr - 1))) 283 counter++; 284 else { 285 if (counter > 1) 286 printf("%s%d", powerstring, counter); 287 if (**ptr) { 288 if (!printedslash) 289 printf(" /"); 290 printedslash = 1; 291 printf(" %s", *ptr); 292 } 293 counter = 1; 294 } 295 } 296 if (counter > 1) 297 printf("%s%d", powerstring, counter); 298 printf("\n"); 299 } 300 301 302 void 303 zeroerror(void) 304 { 305 warnx("unit reduces to zero"); 306 } 307 308 /* 309 Adds the specified string to the unit. 310 Flip is 0 for adding normally, 1 for adding reciprocal. 311 Quantity is 1 if this is a quantity to be converted rather than a pure unit. 312 313 Returns 0 for successful addition, nonzero on error. 314 */ 315 316 int 317 addunit(struct unittype * theunit, const char *toadd, int flip, int quantity) 318 { 319 char *scratch, *savescr; 320 char *item; 321 char *divider, *slash, *offset; 322 int doingtop; 323 324 if (!strlen(toadd)) 325 return 1; 326 327 savescr = scratch = dupstr(toadd); 328 for (slash = scratch + 1; *slash; slash++) 329 if (*slash == '-' && 330 (tolower(*(slash - 1)) != 'e' || 331 !strchr(".0123456789", *(slash + 1)))) 332 *slash = ' '; 333 slash = strchr(scratch, '/'); 334 if (slash) 335 *slash = 0; 336 doingtop = 1; 337 do { 338 item = strtok(scratch, " *\t\n/"); 339 while (item) { 340 if (strchr("0123456789.", *item)) { /* item is a number */ 341 double num, offsetnum; 342 343 if (quantity) 344 theunit->quantity = 1; 345 346 offset = strchr(item, '&'); 347 if (offset) { 348 *offset = 0; 349 offsetnum = atof(offset+1); 350 } else 351 offsetnum = 0.0; 352 353 divider = strchr(item, '|'); 354 if (divider) { 355 *divider = 0; 356 num = atof(item); 357 if (!num) { 358 zeroerror(); 359 return 1; 360 } 361 if (doingtop ^ flip) { 362 theunit->factor *= num; 363 theunit->offset *= num; 364 } else { 365 theunit->factor /= num; 366 theunit->offset /= num; 367 } 368 num = atof(divider + 1); 369 if (!num) { 370 zeroerror(); 371 return 1; 372 } 373 if (doingtop ^ flip) { 374 theunit->factor /= num; 375 theunit->offset /= num; 376 } else { 377 theunit->factor *= num; 378 theunit->offset *= num; 379 } 380 } 381 else { 382 num = atof(item); 383 if (!num) { 384 zeroerror(); 385 return 1; 386 } 387 if (doingtop ^ flip) { 388 theunit->factor *= num; 389 theunit->offset *= num; 390 } else { 391 theunit->factor /= num; 392 theunit->offset /= num; 393 } 394 } 395 if (doingtop ^ flip) 396 theunit->offset += offsetnum; 397 } 398 else { /* item is not a number */ 399 int repeat = 1; 400 401 if (strchr("23456789", 402 item[strlen(item) - 1])) { 403 repeat = item[strlen(item) - 1] - '0'; 404 item[strlen(item) - 1] = 0; 405 } 406 for (; repeat; repeat--) 407 if (addsubunit(doingtop ^ flip ? theunit->numerator : theunit->denominator, item)) 408 return 1; 409 } 410 item = strtok(NULL, " *\t/\n"); 411 } 412 doingtop--; 413 if (slash) { 414 scratch = slash + 1; 415 } 416 else 417 doingtop--; 418 } while (doingtop >= 0); 419 free(savescr); 420 return 0; 421 } 422 423 424 int 425 compare(const void *item1, const void *item2) 426 { 427 return strcmp(*(const char * const *)item1, *(const char * const *)item2); 428 } 429 430 431 void 432 sortunit(struct unittype * theunit) 433 { 434 char **ptr; 435 unsigned int count; 436 437 for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++); 438 qsort(theunit->numerator, count, sizeof(char *), compare); 439 for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++); 440 qsort(theunit->denominator, count, sizeof(char *), compare); 441 } 442 443 444 void 445 cancelunit(struct unittype * theunit) 446 { 447 char **den, **num; 448 int comp; 449 450 den = theunit->denominator; 451 num = theunit->numerator; 452 453 while (*num && *den) { 454 comp = strcmp(*den, *num); 455 if (!comp) { 456 /* if (*den!=NULLUNIT) free(*den); 457 if (*num!=NULLUNIT) free(*num);*/ 458 *den++ = NULLUNIT; 459 *num++ = NULLUNIT; 460 } 461 else if (comp < 0) 462 den++; 463 else 464 num++; 465 } 466 } 467 468 469 470 471 /* 472 Looks up the definition for the specified unit. 473 Returns a pointer to the definition or a null pointer 474 if the specified unit does not appear in the units table. 475 */ 476 477 static char buffer[100]; /* buffer for lookupunit answers with 478 prefixes */ 479 480 char * 481 lookupunit(const char *unit) 482 { 483 int i; 484 char *copy; 485 486 for (i = 0; i < unitcount; i++) { 487 if (!strcmp(unittable[i].uname, unit)) 488 return unittable[i].uval; 489 } 490 491 if (unit[strlen(unit) - 1] == '^') { 492 copy = dupstr(unit); 493 copy[strlen(copy) - 1] = 0; 494 for (i = 0; i < unitcount; i++) { 495 if (!strcmp(unittable[i].uname, copy)) { 496 strlcpy(buffer, copy, sizeof(buffer)); 497 free(copy); 498 return buffer; 499 } 500 } 501 free(copy); 502 } 503 if (unit[strlen(unit) - 1] == 's') { 504 copy = dupstr(unit); 505 copy[strlen(copy) - 1] = 0; 506 for (i = 0; i < unitcount; i++) { 507 if (!strcmp(unittable[i].uname, copy)) { 508 strlcpy(buffer, copy, sizeof(buffer)); 509 free(copy); 510 return buffer; 511 } 512 } 513 if (copy[strlen(copy) - 1] == 'e') { 514 copy[strlen(copy) - 1] = 0; 515 for (i = 0; i < unitcount; i++) { 516 if (!strcmp(unittable[i].uname, copy)) { 517 strlcpy(buffer, copy, sizeof(buffer)); 518 free(copy); 519 return buffer; 520 } 521 } 522 } 523 free(copy); 524 } 525 for (i = 0; i < prefixcount; i++) { 526 size_t len = strlen(prefixtable[i].prefixname); 527 if (!strncmp(prefixtable[i].prefixname, unit, len)) { 528 if (!strlen(unit + len) || lookupunit(unit + len)) { 529 snprintf(buffer, sizeof(buffer), "%s %s", 530 prefixtable[i].prefixval, unit + len); 531 return buffer; 532 } 533 } 534 } 535 return 0; 536 } 537 538 539 540 /* 541 reduces a product of symbolic units to primitive units. 542 The three low bits are used to return flags: 543 544 bit 0 (1) set on if reductions were performed without error. 545 bit 1 (2) set on if no reductions are performed. 546 bit 2 (4) set on if an unknown unit is discovered. 547 */ 548 549 550 #define ERROR 4 551 552 int 553 reduceproduct(struct unittype * theunit, int flip) 554 { 555 556 char *toadd; 557 char **product; 558 int didsomething = 2; 559 560 if (flip) 561 product = theunit->denominator; 562 else 563 product = theunit->numerator; 564 565 for (; *product; product++) { 566 567 for (;;) { 568 if (!strlen(*product)) 569 break; 570 toadd = lookupunit(*product); 571 if (!toadd) { 572 printf("unknown unit '%s'\n", *product); 573 return ERROR; 574 } 575 if (strchr(toadd, PRIMITIVECHAR)) 576 break; 577 didsomething = 1; 578 if (*product != NULLUNIT) { 579 free(*product); 580 *product = NULLUNIT; 581 } 582 if (addunit(theunit, toadd, flip, 0)) 583 return ERROR; 584 } 585 } 586 return didsomething; 587 } 588 589 590 /* 591 Reduces numerator and denominator of the specified unit. 592 Returns 0 on success, or 1 on unknown unit error. 593 */ 594 595 int 596 reduceunit(struct unittype * theunit) 597 { 598 int ret; 599 600 ret = 1; 601 while (ret & 1) { 602 ret = reduceproduct(theunit, 0) | reduceproduct(theunit, 1); 603 if (ret & 4) 604 return 1; 605 } 606 return 0; 607 } 608 609 610 int 611 compareproducts(char **one, char **two) 612 { 613 while (*one || *two) { 614 if (!*one && *two != NULLUNIT) 615 return 1; 616 if (!*two && *one != NULLUNIT) 617 return 1; 618 if (*one == NULLUNIT) 619 one++; 620 else if (*two == NULLUNIT) 621 two++; 622 else if (strcmp(*one, *two)) 623 return 1; 624 else 625 one++, two++; 626 } 627 return 0; 628 } 629 630 631 /* Return zero if units are compatible, nonzero otherwise */ 632 633 int 634 compareunits(struct unittype * first, struct unittype * second) 635 { 636 return 637 compareproducts(first->numerator, second->numerator) || 638 compareproducts(first->denominator, second->denominator); 639 } 640 641 642 int 643 completereduce(struct unittype * unit) 644 { 645 if (reduceunit(unit)) 646 return 1; 647 sortunit(unit); 648 cancelunit(unit); 649 return 0; 650 } 651 652 void 653 showanswer(struct unittype * have, struct unittype * want) 654 { 655 double ans; 656 657 if (compareunits(have, want)) { 658 printf("conformability error\n"); 659 if (verbose) 660 printf("\t%s = ", havestr); 661 else if (!terse) 662 printf("\t"); 663 showunit(have); 664 if (!terse) { 665 if (verbose) 666 printf("\t%s = ", wantstr); 667 else 668 printf("\t"); 669 showunit(want); 670 } 671 } 672 else if (have->offset != want->offset) { 673 if (want->quantity) 674 printf("WARNING: conversion of non-proportional quantities.\n"); 675 if (have->quantity) 676 printf("\t%.8g\n", 677 (have->factor + have->offset-want->offset)/want->factor); 678 else { 679 printf("\t (-> x*%.8g %+.8g)\n\t (<- y*%.8g %+.8g)\n", 680 have->factor / want->factor, 681 (have->offset-want->offset)/want->factor, 682 want->factor / have->factor, 683 (want->offset - have->offset)/have->factor); 684 } 685 } 686 else { 687 ans = have->factor / want->factor; 688 if (verbose) 689 printf("\t%s = %.8g * %s\n", havestr, ans, wantstr); 690 else if (terse) 691 printf("%.8g\n", ans); 692 else 693 printf("\t* %.8g\n", ans); 694 695 if (verbose) 696 printf("\t%s = (1 / %.8g) * %s\n", havestr, 1/ans, wantstr); 697 else if (!terse) 698 printf("\t/ %.8g\n", 1/ans); 699 } 700 } 701 702 703 static void 704 usage(void) 705 { 706 fprintf(stderr, 707 "usage: units [-f unitsfile] [-UVq] [from-unit to-unit]\n"); 708 exit(3); 709 } 710 711 static struct option longopts[] = { 712 {"help", no_argument, NULL, 'h'}, 713 {"file", required_argument, NULL, 'f'}, 714 {"quiet", no_argument, NULL, 'q'}, 715 {"terse", no_argument, NULL, 't'}, 716 {"unitsfile", no_argument, NULL, 'U'}, 717 {"verbose", no_argument, NULL, 'v'}, 718 {"version", no_argument, NULL, 'V'}, 719 { 0, 0, 0, 0 } 720 }; 721 722 723 int 724 main(int argc, char **argv) 725 { 726 727 struct unittype have, want; 728 int optchar; 729 bool quiet; 730 bool readfile; 731 History *inhistory; 732 EditLine *el; 733 HistEvent ev; 734 int inputsz; 735 736 quiet = false; 737 readfile = false; 738 while ((optchar = getopt_long(argc, argv, "+hf:qtvUV", longopts, NULL)) != -1) { 739 switch (optchar) { 740 case 'f': 741 readfile = true; 742 if (strlen(optarg) == 0) 743 readunits(NULL); 744 else 745 readunits(optarg); 746 break; 747 case 'q': 748 quiet = true; 749 break; 750 case 't': 751 terse = true; 752 break; 753 case 'v': 754 verbose = true; 755 break; 756 case 'V': 757 fprintf(stderr, "FreeBSD units\n"); 758 /* FALLTHROUGH */ 759 case 'U': 760 if (access(UNITSFILE, F_OK) == 0) 761 printf("%s\n", UNITSFILE); 762 else 763 printf("Units data file not found"); 764 exit(0); 765 break; 766 case 'h': 767 /* FALLTHROUGH */ 768 769 default: 770 usage(); 771 } 772 } 773 774 if (!readfile) 775 readunits(NULL); 776 777 inhistory = history_init(); 778 el = el_init(argv[0], stdin, stdout, stderr); 779 el_set(el, EL_PROMPT, &prompt); 780 el_set(el, EL_EDITOR, "emacs"); 781 el_set(el, EL_SIGNAL, 1); 782 el_set(el, EL_HIST, history, inhistory); 783 el_source(el, NULL); 784 history(inhistory, &ev, H_SETSIZE, 800); 785 if (inhistory == 0) 786 err(1, "Could not initialize history"); 787 788 if (cap_enter() < 0 && errno != ENOSYS) 789 err(1, "unable to enter capability mode"); 790 791 if (optind == argc - 2) { 792 havestr = argv[optind]; 793 wantstr = argv[optind + 1]; 794 initializeunit(&have); 795 addunit(&have, havestr, 0, 1); 796 completereduce(&have); 797 initializeunit(&want); 798 addunit(&want, wantstr, 0, 1); 799 completereduce(&want); 800 showanswer(&have, &want); 801 } 802 else { 803 if (!quiet) 804 printf("%d units, %d prefixes\n", unitcount, 805 prefixcount); 806 for (;;) { 807 do { 808 initializeunit(&have); 809 if (!quiet) 810 promptstr = "You have: "; 811 havestr = el_gets(el, &inputsz); 812 if (havestr == NULL) 813 exit(0); 814 if (inputsz > 0) 815 history(inhistory, &ev, H_ENTER, 816 havestr); 817 } while (addunit(&have, havestr, 0, 1) || 818 completereduce(&have)); 819 do { 820 initializeunit(&want); 821 if (!quiet) 822 promptstr = "You want: "; 823 wantstr = el_gets(el, &inputsz); 824 if (wantstr == NULL) 825 exit(0); 826 if (inputsz > 0) 827 history(inhistory, &ev, H_ENTER, 828 wantstr); 829 } while (addunit(&want, wantstr, 0, 1) || 830 completereduce(&want)); 831 showanswer(&have, &want); 832 } 833 } 834 835 history_end(inhistory); 836 el_end(el); 837 return (0); 838 } 839