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