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