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