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