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