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