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 free(savescr); 358 return 1; 359 } 360 if (doingtop ^ flip) { 361 theunit->factor *= num; 362 theunit->offset *= num; 363 } else { 364 theunit->factor /= num; 365 theunit->offset /= num; 366 } 367 num = atof(divider + 1); 368 if (!num) { 369 zeroerror(); 370 free(savescr); 371 return 1; 372 } 373 if (doingtop ^ flip) { 374 theunit->factor /= num; 375 theunit->offset /= num; 376 } else { 377 theunit->factor *= num; 378 theunit->offset *= num; 379 } 380 } 381 else { 382 num = atof(item); 383 if (!num) { 384 zeroerror(); 385 free(savescr); 386 return 1; 387 } 388 if (doingtop ^ flip) { 389 theunit->factor *= num; 390 theunit->offset *= num; 391 } else { 392 theunit->factor /= num; 393 theunit->offset /= num; 394 } 395 } 396 if (doingtop ^ flip) 397 theunit->offset += offsetnum; 398 } 399 else { /* item is not a number */ 400 int repeat = 1; 401 402 if (strchr("23456789", 403 item[strlen(item) - 1])) { 404 repeat = item[strlen(item) - 1] - '0'; 405 item[strlen(item) - 1] = 0; 406 } 407 for (; repeat; repeat--) { 408 if (addsubunit(doingtop ^ flip ? theunit->numerator : theunit->denominator, item)) { 409 free(savescr); 410 return 1; 411 } 412 } 413 } 414 item = strtok(NULL, " *\t/\n"); 415 } 416 doingtop--; 417 if (slash) { 418 scratch = slash + 1; 419 } 420 else 421 doingtop--; 422 } while (doingtop >= 0); 423 free(savescr); 424 return 0; 425 } 426 427 428 static int 429 compare(const void *item1, const void *item2) 430 { 431 return strcmp(*(const char * const *)item1, *(const char * const *)item2); 432 } 433 434 435 static void 436 sortunit(struct unittype * theunit) 437 { 438 char **ptr; 439 unsigned int count; 440 441 for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++); 442 qsort(theunit->numerator, count, sizeof(char *), compare); 443 for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++); 444 qsort(theunit->denominator, count, sizeof(char *), compare); 445 } 446 447 448 void 449 cancelunit(struct unittype * theunit) 450 { 451 char **den, **num; 452 int comp; 453 454 den = theunit->denominator; 455 num = theunit->numerator; 456 457 while (*num && *den) { 458 comp = strcmp(*den, *num); 459 if (!comp) { 460 /* if (*den!=NULLUNIT) free(*den); 461 if (*num!=NULLUNIT) free(*num);*/ 462 *den++ = NULLUNIT; 463 *num++ = NULLUNIT; 464 } 465 else if (comp < 0) 466 den++; 467 else 468 num++; 469 } 470 } 471 472 473 474 475 /* 476 Looks up the definition for the specified unit. 477 Returns a pointer to the definition or a null pointer 478 if the specified unit does not appear in the units table. 479 */ 480 481 static char buffer[100]; /* buffer for lookupunit answers with 482 prefixes */ 483 484 char * 485 lookupunit(const char *unit) 486 { 487 int i; 488 char *copy; 489 490 for (i = 0; i < unitcount; i++) { 491 if (!strcmp(unittable[i].uname, unit)) 492 return unittable[i].uval; 493 } 494 495 if (unit[strlen(unit) - 1] == '^') { 496 copy = dupstr(unit); 497 copy[strlen(copy) - 1] = 0; 498 for (i = 0; i < unitcount; i++) { 499 if (!strcmp(unittable[i].uname, copy)) { 500 strlcpy(buffer, copy, sizeof(buffer)); 501 free(copy); 502 return buffer; 503 } 504 } 505 free(copy); 506 } 507 if (unit[strlen(unit) - 1] == 's') { 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 if (copy[strlen(copy) - 1] == 'e') { 518 copy[strlen(copy) - 1] = 0; 519 for (i = 0; i < unitcount; i++) { 520 if (!strcmp(unittable[i].uname, copy)) { 521 strlcpy(buffer, copy, sizeof(buffer)); 522 free(copy); 523 return buffer; 524 } 525 } 526 } 527 free(copy); 528 } 529 for (i = 0; i < prefixcount; i++) { 530 size_t len = strlen(prefixtable[i].prefixname); 531 if (!strncmp(prefixtable[i].prefixname, unit, len)) { 532 if (!strlen(unit + len) || lookupunit(unit + len)) { 533 snprintf(buffer, sizeof(buffer), "%s %s", 534 prefixtable[i].prefixval, unit + len); 535 return buffer; 536 } 537 } 538 } 539 return 0; 540 } 541 542 543 544 /* 545 reduces a product of symbolic units to primitive units. 546 The three low bits are used to return flags: 547 548 bit 0 (1) set on if reductions were performed without error. 549 bit 1 (2) set on if no reductions are performed. 550 bit 2 (4) set on if an unknown unit is discovered. 551 */ 552 553 554 #define ERROR 4 555 556 static int 557 reduceproduct(struct unittype * theunit, int flip) 558 { 559 560 char *toadd; 561 char **product; 562 int didsomething = 2; 563 564 if (flip) 565 product = theunit->denominator; 566 else 567 product = theunit->numerator; 568 569 for (; *product; product++) { 570 571 for (;;) { 572 if (!strlen(*product)) 573 break; 574 toadd = lookupunit(*product); 575 if (!toadd) { 576 printf("unknown unit '%s'\n", *product); 577 return ERROR; 578 } 579 if (strchr(toadd, PRIMITIVECHAR)) 580 break; 581 didsomething = 1; 582 if (*product != NULLUNIT) { 583 free(*product); 584 *product = NULLUNIT; 585 } 586 if (addunit(theunit, toadd, flip, 0)) 587 return ERROR; 588 } 589 } 590 return didsomething; 591 } 592 593 594 /* 595 Reduces numerator and denominator of the specified unit. 596 Returns 0 on success, or 1 on unknown unit error. 597 */ 598 599 static int 600 reduceunit(struct unittype * theunit) 601 { 602 int ret; 603 604 ret = 1; 605 while (ret & 1) { 606 ret = reduceproduct(theunit, 0) | reduceproduct(theunit, 1); 607 if (ret & 4) 608 return 1; 609 } 610 return 0; 611 } 612 613 614 static int 615 compareproducts(char **one, char **two) 616 { 617 while (*one || *two) { 618 if (!*one && *two != NULLUNIT) 619 return 1; 620 if (!*two && *one != NULLUNIT) 621 return 1; 622 if (*one == NULLUNIT) 623 one++; 624 else if (*two == NULLUNIT) 625 two++; 626 else if (strcmp(*one, *two)) 627 return 1; 628 else 629 one++, two++; 630 } 631 return 0; 632 } 633 634 635 /* Return zero if units are compatible, nonzero otherwise */ 636 637 static int 638 compareunits(struct unittype * first, struct unittype * second) 639 { 640 return 641 compareproducts(first->numerator, second->numerator) || 642 compareproducts(first->denominator, second->denominator); 643 } 644 645 646 static int 647 completereduce(struct unittype * unit) 648 { 649 if (reduceunit(unit)) 650 return 1; 651 sortunit(unit); 652 cancelunit(unit); 653 return 0; 654 } 655 656 static void 657 showanswer(struct unittype * have, struct unittype * want) 658 { 659 double ans; 660 char* oformat; 661 662 if (compareunits(have, want)) { 663 printf("conformability error\n"); 664 if (verbose) 665 printf("\t%s = ", havestr); 666 else if (!terse) 667 printf("\t"); 668 showunit(have); 669 if (!terse) { 670 if (verbose) 671 printf("\t%s = ", wantstr); 672 else 673 printf("\t"); 674 showunit(want); 675 } 676 } 677 else if (have->offset != want->offset) { 678 if (want->quantity) 679 printf("WARNING: conversion of non-proportional quantities.\n"); 680 if (have->quantity) { 681 asprintf(&oformat, "\t%s\n", outputformat); 682 printf(oformat, 683 (have->factor + have->offset-want->offset)/want->factor); 684 free(oformat); 685 } 686 else { 687 asprintf(&oformat, "\t (-> x*%sg %sg)\n\t (<- y*%sg %sg)\n", 688 outputformat, outputformat, outputformat, outputformat); 689 printf(oformat, 690 have->factor / want->factor, 691 (have->offset-want->offset)/want->factor, 692 want->factor / have->factor, 693 (want->offset - have->offset)/have->factor); 694 } 695 } 696 else { 697 ans = have->factor / want->factor; 698 699 if (verbose) { 700 printf("\t%s = ", havestr); 701 printf(outputformat, ans); 702 printf(" * %s", wantstr); 703 printf("\n"); 704 } 705 else if (terse) { 706 printf(outputformat, ans); 707 printf("\n"); 708 } 709 else { 710 printf("\t* "); 711 printf(outputformat, ans); 712 printf("\n"); 713 } 714 715 if (verbose) { 716 printf("\t%s = (1 / ", havestr); 717 printf(outputformat, 1/ans); 718 printf(") * %s\n", wantstr); 719 } 720 else if (!terse) { 721 printf("\t/ "); 722 printf(outputformat, 1/ans); 723 printf("\n"); 724 } 725 } 726 } 727 728 729 static void 730 usage(void) 731 { 732 fprintf(stderr, 733 "usage: units [-f unitsfile] [-H historyfile] [-UVq] [from-unit to-unit]\n"); 734 exit(3); 735 } 736 737 static struct option longopts[] = { 738 {"help", no_argument, NULL, 'h'}, 739 {"exponential", no_argument, NULL, 'e'}, 740 {"file", required_argument, NULL, 'f'}, 741 {"history", required_argument, NULL, 'H'}, 742 {"output-format", required_argument, NULL, 'o'}, 743 {"quiet", no_argument, NULL, 'q'}, 744 {"terse", no_argument, NULL, 't'}, 745 {"unitsfile", no_argument, NULL, 'U'}, 746 {"verbose", no_argument, NULL, 'v'}, 747 {"version", no_argument, NULL, 'V'}, 748 { 0, 0, 0, 0 } 749 }; 750 751 752 int 753 main(int argc, char **argv) 754 { 755 756 struct unittype have, want; 757 int optchar; 758 bool quiet; 759 bool readfile; 760 bool quit; 761 History *inhistory; 762 EditLine *el; 763 HistEvent ev; 764 int inputsz; 765 char const * history_file; 766 767 quiet = false; 768 readfile = false; 769 history_file = NULL; 770 outputformat = numfmt; 771 quit = false; 772 while ((optchar = getopt_long(argc, argv, "+ehf:o:qtvH:UV", longopts, NULL)) != -1) { 773 switch (optchar) { 774 case 'e': 775 outputformat = "%6e"; 776 break; 777 case 'f': 778 readfile = true; 779 if (strlen(optarg) == 0) 780 readunits(NULL); 781 else 782 readunits(optarg); 783 break; 784 case 'H': 785 history_file = optarg; 786 break; 787 case 'q': 788 quiet = true; 789 break; 790 case 't': 791 terse = true; 792 break; 793 case 'o': 794 outputformat = optarg; 795 break; 796 case 'v': 797 verbose = true; 798 break; 799 case 'V': 800 fprintf(stderr, "FreeBSD units\n"); 801 /* FALLTHROUGH */ 802 case 'U': 803 if (access(UNITSFILE, F_OK) == 0) 804 printf("%s\n", UNITSFILE); 805 else 806 printf("Units data file not found"); 807 exit(0); 808 break; 809 case 'h': 810 /* FALLTHROUGH */ 811 812 default: 813 usage(); 814 } 815 } 816 817 if (!readfile) 818 readunits(NULL); 819 820 if (optind == argc - 2) { 821 if (cap_enter() < 0 && errno != ENOSYS) 822 err(1, "unable to enter capability mode"); 823 824 havestr = argv[optind]; 825 wantstr = argv[optind + 1]; 826 initializeunit(&have); 827 addunit(&have, havestr, 0, 1); 828 completereduce(&have); 829 initializeunit(&want); 830 addunit(&want, wantstr, 0, 1); 831 completereduce(&want); 832 showanswer(&have, &want); 833 } else { 834 inhistory = history_init(); 835 el = el_init(argv[0], stdin, stdout, stderr); 836 el_set(el, EL_PROMPT, &prompt); 837 el_set(el, EL_EDITOR, "emacs"); 838 el_set(el, EL_SIGNAL, 1); 839 el_set(el, EL_HIST, history, inhistory); 840 el_source(el, NULL); 841 history(inhistory, &ev, H_SETSIZE, 800); 842 if (inhistory == 0) 843 err(1, "Could not initialize history"); 844 845 if (cap_enter() < 0 && errno != ENOSYS) 846 err(1, "unable to enter capability mode"); 847 848 if (!quiet) 849 printf("%d units, %d prefixes\n", unitcount, 850 prefixcount); 851 while (!quit) { 852 do { 853 initializeunit(&have); 854 if (!quiet) 855 promptstr = "You have: "; 856 havestr = el_gets(el, &inputsz); 857 if (havestr == NULL) { 858 quit = true; 859 break; 860 } 861 if (inputsz > 0) 862 history(inhistory, &ev, H_ENTER, 863 havestr); 864 } while (addunit(&have, havestr, 0, 1) || 865 completereduce(&have)); 866 if (quit) { 867 break; 868 } 869 do { 870 initializeunit(&want); 871 if (!quiet) 872 promptstr = "You want: "; 873 wantstr = el_gets(el, &inputsz); 874 if (wantstr == NULL) { 875 quit = true; 876 break; 877 } 878 if (inputsz > 0) 879 history(inhistory, &ev, H_ENTER, 880 wantstr); 881 } while (addunit(&want, wantstr, 0, 1) || 882 completereduce(&want)); 883 if (quit) { 884 break; 885 } 886 showanswer(&have, &want); 887 } 888 889 history_end(inhistory); 890 el_end(el); 891 } 892 893 return (0); 894 } 895