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