units.c revision 1.22 1 /* $NetBSD: units.c,v 1.22 2013/01/01 12:45:06 apb Exp $ */
2
3 /*
4 * units.c Copyright (c) 1993 by Adrian Mariano (adrian (at) 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 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, thedefn/*XXX*/, 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 [-f unitsfile] [-q] [-v] [from-unit to-unit]\n");
798 fprintf(stderr, "\n -f specify units file\n");
799 fprintf(stderr, " -q suppress prompting (quiet)\n");
800 fprintf(stderr, " -v print version number\n");
801 exit(3);
802 }
803
804 int
805 main(int argc, char **argv)
806 {
807
808 struct unittype have, want;
809 char havestr[81], wantstr[81];
810 int optchar;
811 const char *userfile = 0;
812 int list = 0, listexpand = 0;
813 int quiet = 0;
814
815 while ((optchar = getopt(argc, argv, "lLvqf:")) != -1) {
816 switch (optchar) {
817 case 'l':
818 list = 1;
819 break;
820 case 'L':
821 list = 1;
822 listexpand = 1;
823 precision = DBL_DIG;
824 break;
825 case 'f':
826 userfile = optarg;
827 break;
828 case 'q':
829 quiet = 1;
830 break;
831 case 'v':
832 fprintf(stderr, "\n units version %s Copyright (c) 1993 by Adrian Mariano\n",
833 VERSION);
834 fprintf(stderr, " This program may be freely distributed\n");
835 usage();
836 default:
837 usage();
838 break;
839 }
840 }
841
842 argc -= optind;
843 argv += optind;
844
845 if ((argc != 3 && argc != 2 && argc != 0)
846 || (list && argc != 0))
847 usage();
848
849 if (list)
850 errprefix = "/ "; /* set this before reading the file */
851
852 readunits(userfile);
853
854 if (list)
855 return listunits(listexpand);
856
857 if (argc == 3) {
858 strlcpy(havestr, argv[0], sizeof(havestr));
859 strlcat(havestr, " ", sizeof(havestr));
860 strlcat(havestr, argv[1], sizeof(havestr));
861 argc--;
862 argv++;
863 argv[0] = havestr;
864 }
865
866 if (argc == 2) {
867 strlcpy(havestr, argv[0], sizeof(havestr));
868 strlcpy(wantstr, argv[1], sizeof(wantstr));
869 initializeunit(&have);
870 addunit(&have, havestr, 0);
871 completereduce(&have);
872 initializeunit(&want);
873 addunit(&want, wantstr, 0);
874 completereduce(&want);
875 showanswer(&have, &want);
876 }
877 else {
878 if (!quiet)
879 printf("%d units, %d prefixes\n\n", unitcount,
880 prefixcount);
881 for (;;) {
882 do {
883 initializeunit(&have);
884 if (!quiet)
885 printf("You have: ");
886 if (!fgets(havestr, 80, stdin)) {
887 if (!quiet)
888 putchar('\n');
889 exit(0);
890 }
891 } while (addunit(&have, havestr, 0) ||
892 completereduce(&have));
893 do {
894 initializeunit(&want);
895 if (!quiet)
896 printf("You want: ");
897 if (!fgets(wantstr, 80, stdin)) {
898 if (!quiet)
899 putchar('\n');
900 exit(0);
901 }
902 } while (addunit(&want, wantstr, 0) ||
903 completereduce(&want));
904 showanswer(&have, &want);
905 }
906 }
907 return (0);
908 }
909