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