a05e224c4cdb1c8ddff62885e84c5b5e876623ae
[dragonfly.git] / usr.bin / units / units.c
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  * $FreeBSD: src/usr.bin/units/units.c,v 1.6.2.2 2001/03/04 09:22:35 kris Exp $
18  * $DragonFly: src/usr.bin/units/units.c,v 1.4 2006/08/13 19:19:44 swildner Exp $
19  */
20
21 #include <ctype.h>
22 #include <err.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.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 100
38
39 #define MAXSUBUNITS 500
40
41 #define PRIMITIVECHAR '!'
42
43 const char *powerstring = "^";
44
45 struct {
46         char *uname;
47         char *uval;
48 }      unittable[MAXUNITS];
49
50 struct unittype {
51         char *numerator[MAXSUBUNITS];
52         char *denominator[MAXSUBUNITS];
53         double factor;
54 };
55
56 struct {
57         char *prefixname;
58         char *prefixval;
59 }      prefixtable[MAXPREFIXES];
60
61
62 char NULLUNIT[] = "";
63
64 #ifdef MSDOS
65 #define SEPARATOR      ";"
66 #else
67 #define SEPARATOR      ":"
68 #endif
69
70 int unitcount;
71 int prefixcount;
72
73
74 static int       addsubunit(char *[], char *);
75 static int       addunit(struct unittype *, char *, int);
76 static void      cancelunit(struct unittype *);
77 static int       compare(const void *, const void *);
78 static int       compareproducts(char **, char **);
79 static int       compareunits(struct unittype *, struct unittype *);
80 static int       completereduce(struct unittype *);
81 static char     *dupstr(const char *);
82 static void      initializeunit(struct unittype *);
83 static char     *lookupunit(const char *);
84 static void      readunits(const char *);
85 static int       reduceproduct(struct unittype *, int);
86 static int       reduceunit(struct unittype *);
87 static void      showanswer(struct unittype *, struct unittype *);
88 static void      showunit(struct unittype *);
89 static void      sortunit(struct unittype *);
90 static void      usage(void);
91 static void      zeroerror(void);
92
93
94 char *
95 dupstr(const char *str)
96 {
97         char *ret;
98
99         ret = malloc(strlen(str) + 1);
100         if (!ret)
101                 errx(3, "memory allocation error");
102         strcpy(ret, str);
103         return (ret);
104 }
105
106
107 void 
108 readunits(const char *userfile)
109 {
110         FILE *unitfile;
111         char line[512], *lineptr;
112         int len, linenum, i;
113
114         unitcount = 0;
115         linenum = 0;
116
117         if (userfile) {
118                 unitfile = fopen(userfile, "rt");
119                 if (!unitfile)
120                         errx(1, "unable to open units file '%s'", userfile);
121         }
122         else {
123                 unitfile = fopen(UNITSFILE, "rt");
124                 if (!unitfile) {
125                         char *direc, *env;
126                         char filename[1000];
127
128                         env = getenv("PATH");
129                         if (env) {
130                                 direc = strtok(env, SEPARATOR);
131                                 while (direc) {
132                                         snprintf(filename, sizeof(filename),
133                                             "%s/%s", direc, UNITSFILE);
134                                         unitfile = fopen(filename, "rt");
135                                         if (unitfile)
136                                                 break;
137                                         direc = strtok(NULL, SEPARATOR);
138                                 }
139                         }
140                         if (!unitfile)
141                                 errx(1, "can't find units file '%s'", UNITSFILE);
142                 }
143         }
144         while (!feof(unitfile)) {
145                 if (!fgets(line, sizeof(line), unitfile))
146                         break;
147                 linenum++;
148                 lineptr = line;
149                 if (*lineptr == '/')
150                         continue;
151                 lineptr += strspn(lineptr, " \n\t");
152                 len = strcspn(lineptr, " \n\t");
153                 lineptr[len] = 0;
154                 if (!strlen(lineptr))
155                         continue;
156                 if (lineptr[strlen(lineptr) - 1] == '-') { /* it's a prefix */
157                         if (prefixcount == MAXPREFIXES) {
158                                 warnx("memory for prefixes exceeded in line %d", linenum);
159                                 continue;
160                         }
161                         lineptr[strlen(lineptr) - 1] = 0;
162                         prefixtable[prefixcount].prefixname = dupstr(lineptr);
163                         for (i = 0; i < prefixcount; i++)
164                                 if (!strcmp(prefixtable[i].prefixname, lineptr)) {
165                                         warnx("redefinition of prefix '%s' on line %d ignored",
166                                             lineptr, linenum);
167                                         continue;
168                                 }
169                         lineptr += len + 1;
170                         lineptr += strspn(lineptr, " \n\t");
171                         len = strcspn(lineptr, "\n\t");
172                         if (len == 0) {
173                                 warnx("unexpected end of prefix on line %d",
174                                     linenum);
175                                 continue;
176                         }
177                         lineptr[len] = 0;
178                         prefixtable[prefixcount++].prefixval = dupstr(lineptr);
179                 }
180                 else {          /* it's not a prefix */
181                         if (unitcount == MAXUNITS) {
182                                 warnx("memory for units exceeded in line %d", linenum);
183                                 continue;
184                         }
185                         unittable[unitcount].uname = dupstr(lineptr);
186                         for (i = 0; i < unitcount; i++)
187                                 if (!strcmp(unittable[i].uname, lineptr)) {
188                                         warnx("redefinition of unit '%s' on line %d ignored",
189                                             lineptr, linenum);
190                                         continue;
191                                 }
192                         lineptr += len + 1;
193                         lineptr += strspn(lineptr, " \n\t");
194                         if (!strlen(lineptr)) {
195                                 warnx("unexpected end of unit on line %d",
196                                     linenum);
197                                 continue;
198                         }
199                         len = strcspn(lineptr, "\n\t");
200                         lineptr[len] = 0;
201                         unittable[unitcount++].uval = dupstr(lineptr);
202                 }
203         }
204         fclose(unitfile);
205 }
206
207 void 
208 initializeunit(struct unittype * theunit)
209 {
210         theunit->factor = 1.0;
211         theunit->numerator[0] = theunit->denominator[0] = NULL;
212 }
213
214
215 int 
216 addsubunit(char *product[], char *toadd)
217 {
218         char **ptr;
219
220         for (ptr = product; *ptr && *ptr != NULLUNIT; ptr++);
221         if (ptr >= product + MAXSUBUNITS) {
222                 warnx("memory overflow in unit reduction");
223                 return 1;
224         }
225         if (!*ptr)
226                 *(ptr + 1) = 0;
227         *ptr = dupstr(toadd);
228         return 0;
229 }
230
231
232 void 
233 showunit(struct unittype * theunit)
234 {
235         char **ptr;
236         int printedslash;
237         int counter = 1;
238
239         printf("\t%.8g", theunit->factor);
240         for (ptr = theunit->numerator; *ptr; ptr++) {
241                 if (ptr > theunit->numerator && **ptr &&
242                     !strcmp(*ptr, *(ptr - 1)))
243                         counter++;
244                 else {
245                         if (counter > 1)
246                                 printf("%s%d", powerstring, counter);
247                         if (**ptr)
248                                 printf(" %s", *ptr);
249                         counter = 1;
250                 }
251         }
252         if (counter > 1)
253                 printf("%s%d", powerstring, counter);
254         counter = 1;
255         printedslash = 0;
256         for (ptr = theunit->denominator; *ptr; ptr++) {
257                 if (ptr > theunit->denominator && **ptr &&
258                     !strcmp(*ptr, *(ptr - 1)))
259                         counter++;
260                 else {
261                         if (counter > 1)
262                                 printf("%s%d", powerstring, counter);
263                         if (**ptr) {
264                                 if (!printedslash)
265                                         printf(" /");
266                                 printedslash = 1;
267                                 printf(" %s", *ptr);
268                         }
269                         counter = 1;
270                 }
271         }
272         if (counter > 1)
273                 printf("%s%d", powerstring, counter);
274         printf("\n");
275 }
276
277
278 void 
279 zeroerror(void)
280 {
281         warnx("unit reduces to zero");
282 }
283
284 /*
285    Adds the specified string to the unit.
286    Flip is 0 for adding normally, 1 for adding reciprocal.
287
288    Returns 0 for successful addition, nonzero on error.
289 */
290
291 int 
292 addunit(struct unittype * theunit, char *toadd, int flip)
293 {
294         char *scratch, *savescr;
295         char *item;
296         char *divider, *slash;
297         int doingtop;
298
299         if (!strlen(toadd))
300                 return 1;
301         
302         savescr = scratch = dupstr(toadd);
303         for (slash = scratch + 1; *slash; slash++)
304                 if (*slash == '-' &&
305                     (tolower(*(slash - 1)) != 'e' ||
306                     !strchr(".0123456789", *(slash + 1))))
307                         *slash = ' ';
308         slash = strchr(scratch, '/');
309         if (slash)
310                 *slash = 0;
311         doingtop = 1;
312         do {
313                 item = strtok(scratch, " *\t\n/");
314                 while (item) {
315                         if (strchr("0123456789.", *item)) { /* item is a number */
316                                 double num;
317
318                                 divider = strchr(item, '|');
319                                 if (divider) {
320                                         *divider = 0;
321                                         num = atof(item);
322                                         if (!num) {
323                                                 zeroerror();
324                                                 return 1;
325                                         }
326                                         if (doingtop ^ flip)
327                                                 theunit->factor *= num;
328                                         else
329                                                 theunit->factor /= num;
330                                         num = atof(divider + 1);
331                                         if (!num) {
332                                                 zeroerror();
333                                                 return 1;
334                                         }
335                                         if (doingtop ^ flip)
336                                                 theunit->factor /= num;
337                                         else
338                                                 theunit->factor *= num;
339                                 }
340                                 else {
341                                         num = atof(item);
342                                         if (!num) {
343                                                 zeroerror();
344                                                 return 1;
345                                         }
346                                         if (doingtop ^ flip)
347                                                 theunit->factor *= num;
348                                         else
349                                                 theunit->factor /= num;
350
351                                 }
352                         }
353                         else {  /* item is not a number */
354                                 int repeat = 1;
355
356                                 if (strchr("23456789",
357                                     item[strlen(item) - 1])) {
358                                         repeat = item[strlen(item) - 1] - '0';
359                                         item[strlen(item) - 1] = 0;
360                                 }
361                                 for (; repeat; repeat--)
362                                         if (addsubunit(doingtop ^ flip ? theunit->numerator : theunit->denominator, item))
363                                                 return 1;
364                         }
365                         item = strtok(NULL, " *\t/\n");
366                 }
367                 doingtop--;
368                 if (slash) {
369                         scratch = slash + 1;
370                 }
371                 else
372                         doingtop--;
373         } while (doingtop >= 0);
374         free(savescr);
375         return 0;
376 }
377
378
379 int 
380 compare(const void *item1, const void *item2)
381 {
382         return strcmp(*(const char * const *)item1, *(const char * const *)item2);
383 }
384
385
386 void 
387 sortunit(struct unittype * theunit)
388 {
389         char **ptr;
390         unsigned int count;
391
392         for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++);
393         qsort(theunit->numerator, count, sizeof(char *), compare);
394         for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++);
395         qsort(theunit->denominator, count, sizeof(char *), compare);
396 }
397
398
399 void 
400 cancelunit(struct unittype * theunit)
401 {
402         char **den, **num;
403         int comp;
404
405         den = theunit->denominator;
406         num = theunit->numerator;
407
408         while (*num && *den) {
409                 comp = strcmp(*den, *num);
410                 if (!comp) {
411 /*      if (*den!=NULLUNIT) free(*den);
412       if (*num!=NULLUNIT) free(*num);*/
413                         *den++ = NULLUNIT;
414                         *num++ = NULLUNIT;
415                 }
416                 else if (comp < 0)
417                         den++;
418                 else
419                         num++;
420         }
421 }
422
423
424
425
426 /*
427    Looks up the definition for the specified unit.
428    Returns a pointer to the definition or a null pointer
429    if the specified unit does not appear in the units table.
430 */
431
432 static char buffer[100];        /* buffer for lookupunit answers with
433                                    prefixes */
434
435 char *
436 lookupunit(const char *unit)
437 {
438         int i;
439         char *copy;
440
441         for (i = 0; i < unitcount; i++) {
442                 if (!strcmp(unittable[i].uname, unit))
443                         return unittable[i].uval;
444         }
445
446         if (unit[strlen(unit) - 1] == '^') {
447                 copy = dupstr(unit);
448                 copy[strlen(copy) - 1] = 0;
449                 for (i = 0; i < unitcount; i++) {
450                         if (!strcmp(unittable[i].uname, copy)) {
451                                 strlcpy(buffer, copy, sizeof(buffer));
452                                 free(copy);
453                                 return buffer;
454                         }
455                 }
456                 free(copy);
457         }
458         if (unit[strlen(unit) - 1] == 's') {
459                 copy = dupstr(unit);
460                 copy[strlen(copy) - 1] = 0;
461                 for (i = 0; i < unitcount; i++) {
462                         if (!strcmp(unittable[i].uname, copy)) {
463                                 strlcpy(buffer, copy, sizeof(buffer));
464                                 free(copy);
465                                 return buffer;
466                         }
467                 }
468                 if (copy[strlen(copy) - 1] == 'e') {
469                         copy[strlen(copy) - 1] = 0;
470                         for (i = 0; i < unitcount; i++) {
471                                 if (!strcmp(unittable[i].uname, copy)) {
472                                         strlcpy(buffer, copy, sizeof(buffer));
473                                         free(copy);
474                                         return buffer;
475                                 }
476                         }
477                 }
478                 free(copy);
479         }
480         for (i = 0; i < prefixcount; i++) {
481                 size_t len = strlen(prefixtable[i].prefixname);
482                 if (!strncmp(prefixtable[i].prefixname, unit, len)) {
483                         if (!strlen(unit + len) || lookupunit(unit + len)) {
484                                 snprintf(buffer, sizeof(buffer), "%s %s",
485                                     prefixtable[i].prefixval, unit + len);
486                                 return buffer;
487                         }
488                 }
489         }
490         return 0;
491 }
492
493
494
495 /*
496    reduces a product of symbolic units to primitive units.
497    The three low bits are used to return flags:
498
499      bit 0 (1) set on if reductions were performed without error.
500      bit 1 (2) set on if no reductions are performed.
501      bit 2 (4) set on if an unknown unit is discovered.
502 */
503
504
505 #define ERROR 4
506
507 int 
508 reduceproduct(struct unittype * theunit, int flip)
509 {
510
511         char *toadd;
512         char **product;
513         int didsomething = 2;
514
515         if (flip)
516                 product = theunit->denominator;
517         else
518                 product = theunit->numerator;
519
520         for (; *product; product++) {
521
522                 for (;;) {
523                         if (!strlen(*product))
524                                 break;
525                         toadd = lookupunit(*product);
526                         if (!toadd) {
527                                 printf("unknown unit '%s'\n", *product);
528                                 return ERROR;
529                         }
530                         if (strchr(toadd, PRIMITIVECHAR))
531                                 break;
532                         didsomething = 1;
533                         if (*product != NULLUNIT) {
534                                 free(*product);
535                                 *product = NULLUNIT;
536                         }
537                         if (addunit(theunit, toadd, flip))
538                                 return ERROR;
539                 }
540         }
541         return didsomething;
542 }
543
544
545 /*
546    Reduces numerator and denominator of the specified unit.
547    Returns 0 on success, or 1 on unknown unit error.
548 */
549
550 int 
551 reduceunit(struct unittype * theunit)
552 {
553         int ret;
554
555         ret = 1;
556         while (ret & 1) {
557                 ret = reduceproduct(theunit, 0) | reduceproduct(theunit, 1);
558                 if (ret & 4)
559                         return 1;
560         }
561         return 0;
562 }
563
564
565 int 
566 compareproducts(char **one, char **two)
567 {
568         while (*one || *two) {
569                 if (!*one && *two != NULLUNIT)
570                         return 1;
571                 if (!*two && *one != NULLUNIT)
572                         return 1;
573                 if (*one == NULLUNIT)
574                         one++;
575                 else if (*two == NULLUNIT)
576                         two++;
577                 else if (strcmp(*one, *two))
578                         return 1;
579                 else
580                         one++, two++;
581         }
582         return 0;
583 }
584
585
586 /* Return zero if units are compatible, nonzero otherwise */
587
588 int 
589 compareunits(struct unittype * first, struct unittype * second)
590 {
591         return
592         compareproducts(first->numerator, second->numerator) ||
593         compareproducts(first->denominator, second->denominator);
594 }
595
596
597 int 
598 completereduce(struct unittype * unit)
599 {
600         if (reduceunit(unit))
601                 return 1;
602         sortunit(unit);
603         cancelunit(unit);
604         return 0;
605 }
606
607
608 void 
609 showanswer(struct unittype * have, struct unittype * want)
610 {
611         if (compareunits(have, want)) {
612                 printf("conformability error\n");
613                 showunit(have);
614                 showunit(want);
615         }
616         else
617                 printf("\t* %.8g\n\t/ %.8g\n", have->factor / want->factor,
618                     want->factor / have->factor);
619 }
620
621
622 void 
623 usage(void)
624 {
625         fprintf(stderr,
626                 "usage: units [-f unitsfile] [-q] [-v] [from-unit to-unit]\n");
627         exit(3);
628 }
629
630
631 int
632 main(int argc, char **argv)
633 {
634
635         struct unittype have, want;
636         char havestr[81], wantstr[81];
637         int optchar;
638         char *userfile = 0;
639         int quiet = 0;
640
641         while ((optchar = getopt(argc, argv, "vqf:")) != -1) {
642                 switch (optchar) {
643                 case 'f':
644                         userfile = optarg;
645                         break;
646                 case 'q':
647                         quiet = 1;
648                         break;
649                 case 'v':
650                         fprintf(stderr, "\n  units version %s  Copyright (c) 1993 by Adrian Mariano\n",
651                             VERSION);
652                         fprintf(stderr, "                    This program may be freely distributed\n");
653                         usage();
654                 default:
655                         usage();
656                         break;
657                 }
658         }
659
660         if (optind != argc - 2 && optind != argc)
661                 usage();
662
663         readunits(userfile);
664
665         if (optind == argc - 2) {
666                 strlcpy(havestr, argv[optind], sizeof(havestr));
667                 strlcpy(wantstr, argv[optind + 1], sizeof(wantstr));
668                 initializeunit(&have);
669                 addunit(&have, havestr, 0);
670                 completereduce(&have);
671                 initializeunit(&want);
672                 addunit(&want, wantstr, 0);
673                 completereduce(&want);
674                 showanswer(&have, &want);
675         }
676         else {
677                 if (!quiet)
678                         printf("%d units, %d prefixes\n", unitcount,
679                             prefixcount);
680                 for (;;) {
681                         do {
682                                 initializeunit(&have);
683                                 if (!quiet)
684                                         printf("You have: ");
685                                 if (!fgets(havestr, sizeof(havestr), stdin)) {
686                                         if (!quiet)
687                                                 putchar('\n');
688                                         exit(0);
689                                 }
690                         } while (addunit(&have, havestr, 0) ||
691                             completereduce(&have));
692                         do {
693                                 initializeunit(&want);
694                                 if (!quiet)
695                                         printf("You want: ");
696                                 if (!fgets(wantstr, sizeof(wantstr), stdin)) {
697                                         if (!quiet)
698                                                 putchar('\n');
699                                         exit(0);
700                                 }
701                         } while (addunit(&want, wantstr, 0) ||
702                             completereduce(&want));
703                         showanswer(&have, &want);
704                 }
705         }
706
707         return(0);
708 }