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