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