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