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