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