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