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