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