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