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