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