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 continue; 176 } 177 lineptr += len + 1; 178 lineptr += strspn(lineptr, " \n\t"); 179 len = strcspn(lineptr, "\n\t"); 180 if (len == 0) { 181 warnx("unexpected end of prefix on line %d", 182 linenum); 183 continue; 184 } 185 lineptr[len] = 0; 186 prefixtable[prefixcount++].prefixval = dupstr(lineptr); 187 } 188 else { /* it's not a prefix */ 189 if (unitcount == MAXUNITS) { 190 warnx("memory for units exceeded in line %d", linenum); 191 continue; 192 } 193 unittable[unitcount].uname = dupstr(lineptr); 194 for (i = 0; i < unitcount; i++) 195 if (!strcmp(unittable[i].uname, lineptr)) { 196 warnx("redefinition of unit '%s' on line %d ignored", 197 lineptr, linenum); 198 continue; 199 } 200 lineptr += len + 1; 201 lineptr += strspn(lineptr, " \n\t"); 202 if (!strlen(lineptr)) { 203 warnx("unexpected end of unit on line %d", 204 linenum); 205 continue; 206 } 207 len = strcspn(lineptr, "\n\t"); 208 lineptr[len] = 0; 209 unittable[unitcount++].uval = dupstr(lineptr); 210 } 211 } 212 fclose(unitfile); 213 } 214 215 void 216 initializeunit(struct unittype * theunit) 217 { 218 theunit->numerator[0] = theunit->denominator[0] = NULL; 219 theunit->factor = 1.0; 220 theunit->offset = 0.0; 221 theunit->quantity = 0; 222 } 223 224 225 int 226 addsubunit(char *product[], char *toadd) 227 { 228 char **ptr; 229 230 for (ptr = product; *ptr && *ptr != NULLUNIT; ptr++); 231 if (ptr >= product + MAXSUBUNITS) { 232 warnx("memory overflow in unit reduction"); 233 return 1; 234 } 235 if (!*ptr) 236 *(ptr + 1) = NULL; 237 *ptr = dupstr(toadd); 238 return 0; 239 } 240 241 242 void 243 showunit(struct unittype * theunit) 244 { 245 char **ptr; 246 int printedslash; 247 int counter = 1; 248 249 printf("%.8g", theunit->factor); 250 if (theunit->offset) 251 printf("&%.8g", theunit->offset); 252 for (ptr = theunit->numerator; *ptr; ptr++) { 253 if (ptr > theunit->numerator && **ptr && 254 !strcmp(*ptr, *(ptr - 1))) 255 counter++; 256 else { 257 if (counter > 1) 258 printf("%s%d", powerstring, counter); 259 if (**ptr) 260 printf(" %s", *ptr); 261 counter = 1; 262 } 263 } 264 if (counter > 1) 265 printf("%s%d", powerstring, counter); 266 counter = 1; 267 printedslash = 0; 268 for (ptr = theunit->denominator; *ptr; ptr++) { 269 if (ptr > theunit->denominator && **ptr && 270 !strcmp(*ptr, *(ptr - 1))) 271 counter++; 272 else { 273 if (counter > 1) 274 printf("%s%d", powerstring, counter); 275 if (**ptr) { 276 if (!printedslash) 277 printf(" /"); 278 printedslash = 1; 279 printf(" %s", *ptr); 280 } 281 counter = 1; 282 } 283 } 284 if (counter > 1) 285 printf("%s%d", powerstring, counter); 286 printf("\n"); 287 } 288 289 290 void 291 zeroerror(void) 292 { 293 warnx("unit reduces to zero"); 294 } 295 296 /* 297 Adds the specified string to the unit. 298 Flip is 0 for adding normally, 1 for adding reciprocal. 299 Quantity is 1 if this is a quantity to be converted rather than a pure unit. 300 301 Returns 0 for successful addition, nonzero on error. 302 */ 303 304 int 305 addunit(struct unittype * theunit, const char *toadd, int flip, int quantity) 306 { 307 char *scratch, *savescr; 308 char *item; 309 char *divider, *slash, *offset; 310 int doingtop; 311 312 if (!strlen(toadd)) 313 return 1; 314 315 savescr = scratch = dupstr(toadd); 316 for (slash = scratch + 1; *slash; slash++) 317 if (*slash == '-' && 318 (tolower(*(slash - 1)) != 'e' || 319 !strchr(".0123456789", *(slash + 1)))) 320 *slash = ' '; 321 slash = strchr(scratch, '/'); 322 if (slash) 323 *slash = 0; 324 doingtop = 1; 325 do { 326 item = strtok(scratch, " *\t\n/"); 327 while (item) { 328 if (strchr("0123456789.", *item)) { /* item is a number */ 329 double num, offsetnum; 330 331 if (quantity) 332 theunit->quantity = 1; 333 334 offset = strchr(item, '&'); 335 if (offset) { 336 *offset = 0; 337 offsetnum = atof(offset+1); 338 } else 339 offsetnum = 0.0; 340 341 divider = strchr(item, '|'); 342 if (divider) { 343 *divider = 0; 344 num = atof(item); 345 if (!num) { 346 zeroerror(); 347 return 1; 348 } 349 if (doingtop ^ flip) { 350 theunit->factor *= num; 351 theunit->offset *= num; 352 } else { 353 theunit->factor /= num; 354 theunit->offset /= num; 355 } 356 num = atof(divider + 1); 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 } 369 else { 370 num = atof(item); 371 if (!num) { 372 zeroerror(); 373 return 1; 374 } 375 if (doingtop ^ flip) { 376 theunit->factor *= num; 377 theunit->offset *= num; 378 } else { 379 theunit->factor /= num; 380 theunit->offset /= num; 381 } 382 } 383 if (doingtop ^ flip) 384 theunit->offset += offsetnum; 385 } 386 else { /* item is not a number */ 387 int repeat = 1; 388 389 if (strchr("23456789", 390 item[strlen(item) - 1])) { 391 repeat = item[strlen(item) - 1] - '0'; 392 item[strlen(item) - 1] = 0; 393 } 394 for (; repeat; repeat--) 395 if (addsubunit(doingtop ^ flip ? theunit->numerator : theunit->denominator, item)) 396 return 1; 397 } 398 item = strtok(NULL, " *\t/\n"); 399 } 400 doingtop--; 401 if (slash) { 402 scratch = slash + 1; 403 } 404 else 405 doingtop--; 406 } while (doingtop >= 0); 407 free(savescr); 408 return 0; 409 } 410 411 412 int 413 compare(const void *item1, const void *item2) 414 { 415 return strcmp(*(const char * const *)item1, *(const char * const *)item2); 416 } 417 418 419 void 420 sortunit(struct unittype * theunit) 421 { 422 char **ptr; 423 unsigned int count; 424 425 for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++); 426 qsort(theunit->numerator, count, sizeof(char *), compare); 427 for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++); 428 qsort(theunit->denominator, count, sizeof(char *), compare); 429 } 430 431 432 void 433 cancelunit(struct unittype * theunit) 434 { 435 char **den, **num; 436 int comp; 437 438 den = theunit->denominator; 439 num = theunit->numerator; 440 441 while (*num && *den) { 442 comp = strcmp(*den, *num); 443 if (!comp) { 444 /* if (*den!=NULLUNIT) free(*den); 445 if (*num!=NULLUNIT) free(*num);*/ 446 *den++ = NULLUNIT; 447 *num++ = NULLUNIT; 448 } 449 else if (comp < 0) 450 den++; 451 else 452 num++; 453 } 454 } 455 456 457 458 459 /* 460 Looks up the definition for the specified unit. 461 Returns a pointer to the definition or a null pointer 462 if the specified unit does not appear in the units table. 463 */ 464 465 static char buffer[100]; /* buffer for lookupunit answers with 466 prefixes */ 467 468 char * 469 lookupunit(const char *unit) 470 { 471 int i; 472 char *copy; 473 474 for (i = 0; i < unitcount; i++) { 475 if (!strcmp(unittable[i].uname, unit)) 476 return unittable[i].uval; 477 } 478 479 if (unit[strlen(unit) - 1] == '^') { 480 copy = dupstr(unit); 481 copy[strlen(copy) - 1] = 0; 482 for (i = 0; i < unitcount; i++) { 483 if (!strcmp(unittable[i].uname, copy)) { 484 strlcpy(buffer, copy, sizeof(buffer)); 485 free(copy); 486 return buffer; 487 } 488 } 489 free(copy); 490 } 491 if (unit[strlen(unit) - 1] == 's') { 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 if (copy[strlen(copy) - 1] == 'e') { 502 copy[strlen(copy) - 1] = 0; 503 for (i = 0; i < unitcount; i++) { 504 if (!strcmp(unittable[i].uname, copy)) { 505 strlcpy(buffer, copy, sizeof(buffer)); 506 free(copy); 507 return buffer; 508 } 509 } 510 } 511 free(copy); 512 } 513 for (i = 0; i < prefixcount; i++) { 514 size_t len = strlen(prefixtable[i].prefixname); 515 if (!strncmp(prefixtable[i].prefixname, unit, len)) { 516 if (!strlen(unit + len) || lookupunit(unit + len)) { 517 snprintf(buffer, sizeof(buffer), "%s %s", 518 prefixtable[i].prefixval, unit + len); 519 return buffer; 520 } 521 } 522 } 523 return 0; 524 } 525 526 527 528 /* 529 reduces a product of symbolic units to primitive units. 530 The three low bits are used to return flags: 531 532 bit 0 (1) set on if reductions were performed without error. 533 bit 1 (2) set on if no reductions are performed. 534 bit 2 (4) set on if an unknown unit is discovered. 535 */ 536 537 538 #define ERROR 4 539 540 int 541 reduceproduct(struct unittype * theunit, int flip) 542 { 543 544 char *toadd; 545 char **product; 546 int didsomething = 2; 547 548 if (flip) 549 product = theunit->denominator; 550 else 551 product = theunit->numerator; 552 553 for (; *product; product++) { 554 555 for (;;) { 556 if (!strlen(*product)) 557 break; 558 toadd = lookupunit(*product); 559 if (!toadd) { 560 printf("unknown unit '%s'\n", *product); 561 return ERROR; 562 } 563 if (strchr(toadd, PRIMITIVECHAR)) 564 break; 565 didsomething = 1; 566 if (*product != NULLUNIT) { 567 free(*product); 568 *product = NULLUNIT; 569 } 570 if (addunit(theunit, toadd, flip, 0)) 571 return ERROR; 572 } 573 } 574 return didsomething; 575 } 576 577 578 /* 579 Reduces numerator and denominator of the specified unit. 580 Returns 0 on success, or 1 on unknown unit error. 581 */ 582 583 int 584 reduceunit(struct unittype * theunit) 585 { 586 int ret; 587 588 ret = 1; 589 while (ret & 1) { 590 ret = reduceproduct(theunit, 0) | reduceproduct(theunit, 1); 591 if (ret & 4) 592 return 1; 593 } 594 return 0; 595 } 596 597 598 int 599 compareproducts(char **one, char **two) 600 { 601 while (*one || *two) { 602 if (!*one && *two != NULLUNIT) 603 return 1; 604 if (!*two && *one != NULLUNIT) 605 return 1; 606 if (*one == NULLUNIT) 607 one++; 608 else if (*two == NULLUNIT) 609 two++; 610 else if (strcmp(*one, *two)) 611 return 1; 612 else 613 one++, two++; 614 } 615 return 0; 616 } 617 618 619 /* Return zero if units are compatible, nonzero otherwise */ 620 621 int 622 compareunits(struct unittype * first, struct unittype * second) 623 { 624 return 625 compareproducts(first->numerator, second->numerator) || 626 compareproducts(first->denominator, second->denominator); 627 } 628 629 630 int 631 completereduce(struct unittype * unit) 632 { 633 if (reduceunit(unit)) 634 return 1; 635 sortunit(unit); 636 cancelunit(unit); 637 return 0; 638 } 639 640 void 641 showanswer(struct unittype * have, struct unittype * want) 642 { 643 double ans; 644 645 if (compareunits(have, want)) { 646 printf("conformability error\n"); 647 if (verbose) 648 printf("\t%s = ", havestr); 649 else 650 printf("\t"); 651 showunit(have); 652 if (verbose) 653 printf("\t%s = ", wantstr); 654 else 655 printf("\t"); 656 showunit(want); 657 } 658 else if (have->offset != want->offset) { 659 if (want->quantity) 660 printf("WARNING: conversion of non-proportional quantities.\n"); 661 if (have->quantity) 662 printf("\t%.8g\n", 663 (have->factor + have->offset-want->offset)/want->factor); 664 else { 665 printf("\t (-> x*%.8g %+.8g)\n\t (<- y*%.8g %+.8g)\n", 666 have->factor / want->factor, 667 (have->offset-want->offset)/want->factor, 668 want->factor / have->factor, 669 (want->offset - have->offset)/have->factor); 670 } 671 } 672 else { 673 ans = have->factor / want->factor; 674 if (verbose) 675 printf("\t%s = %.8g * %s\n", havestr, ans, wantstr); 676 else 677 printf("\t* %.8g\n", ans); 678 679 if (verbose) 680 printf("\t%s = (1 / %.8g) * %s\n", havestr, 1/ans, wantstr); 681 else 682 printf("\t/ %.8g\n", 1/ans); 683 } 684 } 685 686 687 void 688 usage(void) 689 { 690 fprintf(stderr, 691 "usage: units [-f unitsfile] [-UVq] [from-unit to-unit]\n"); 692 exit(3); 693 } 694 695 696 int 697 main(int argc, char **argv) 698 { 699 700 struct unittype have, want; 701 int optchar; 702 bool quiet; 703 bool readfile; 704 History *inhistory; 705 EditLine *el; 706 HistEvent ev; 707 int inputsz; 708 709 quiet = false; 710 readfile = false; 711 while ((optchar = getopt(argc, argv, "f:qvUV")) != -1) { 712 switch (optchar) { 713 case 'f': 714 readfile = true; 715 if (strlen(optarg) == 0) 716 readunits(NULL); 717 else 718 readunits(optarg); 719 break; 720 case 'q': 721 quiet = true; 722 break; 723 case 'v': 724 verbose = true; 725 break; 726 case 'V': 727 fprintf(stderr, "FreeBSD units\n"); 728 /* FALLTHROUGH */ 729 case 'U': 730 if (access(UNITSFILE, F_OK) == 0) 731 printf("%s\n", UNITSFILE); 732 else 733 printf("Units data file not found"); 734 exit(0); 735 break; 736 default: 737 usage(); 738 } 739 } 740 741 if (!readfile) 742 readunits(NULL); 743 744 inhistory = history_init(); 745 el = el_init(argv[0], stdin, stdout, stderr); 746 el_set(el, EL_PROMPT, &prompt); 747 el_set(el, EL_EDITOR, "emacs"); 748 el_set(el, EL_SIGNAL, 1); 749 el_set(el, EL_HIST, history, inhistory); 750 el_source(el, NULL); 751 history(inhistory, &ev, H_SETSIZE, 800); 752 if (inhistory == 0) 753 err(1, "Could not initialize history"); 754 755 if (optind == argc - 2) { 756 havestr = argv[optind]; 757 wantstr = argv[optind + 1]; 758 initializeunit(&have); 759 addunit(&have, havestr, 0, 1); 760 completereduce(&have); 761 initializeunit(&want); 762 addunit(&want, wantstr, 0, 1); 763 completereduce(&want); 764 showanswer(&have, &want); 765 } 766 else { 767 if (!quiet) 768 printf("%d units, %d prefixes\n", unitcount, 769 prefixcount); 770 for (;;) { 771 do { 772 initializeunit(&have); 773 if (!quiet) 774 promptstr = "You have: "; 775 havestr = el_gets(el, &inputsz); 776 if (havestr == NULL) 777 exit(0); 778 if (inputsz > 0) 779 history(inhistory, &ev, H_ENTER, 780 havestr); 781 } while (addunit(&have, havestr, 0, 1) || 782 completereduce(&have)); 783 do { 784 initializeunit(&want); 785 if (!quiet) 786 promptstr = "You want: "; 787 wantstr = el_gets(el, &inputsz); 788 if (wantstr == NULL) 789 exit(0); 790 if (inputsz > 0) 791 history(inhistory, &ev, H_ENTER, 792 wantstr); 793 } while (addunit(&want, wantstr, 0, 1) || 794 completereduce(&want)); 795 showanswer(&have, &want); 796 } 797 } 798 799 history_end(inhistory); 800 el_end(el); 801 return(0); 802 } 803