xref: /freebsd/usr.bin/units/units.c (revision e627b39baccd1ec9129690167cf5e6d860509655)
1 /*	$Id: units.c,v 1.1.1.1 1996/06/08 03:43:43 alex Exp $	*/
2 
3 /*
4  * units.c   Copyright (c) 1993 by Adrian Mariano (adrian@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 <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 	int optchar;
635 	char *userfile = 0;
636 	int quiet = 0;
637 
638 	extern char *optarg;
639 	extern int optind;
640 
641 	while ((optchar = getopt(argc, argv, "vqf:")) != -1) {
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 	return(0);
708 }
709