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