units.c revision 73229
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
19static const char rcsid[] =
20  "$FreeBSD: head/usr.bin/units/units.c 73229 2001-02-28 15:57:38Z dwmalone $";
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
45const char *powerstring = "^";
46
47struct {
48	char *uname;
49	char *uval;
50}      unittable[MAXUNITS];
51
52struct unittype {
53	char *numerator[MAXSUBUNITS];
54	char *denominator[MAXSUBUNITS];
55	double factor;
56};
57
58struct {
59	char *prefixname;
60	char *prefixval;
61}      prefixtable[MAXPREFIXES];
62
63
64char NULLUNIT[] = "";
65
66#ifdef MSDOS
67#define SEPARATOR      ";"
68#else
69#define SEPARATOR      ":"
70#endif
71
72int unitcount;
73int prefixcount;
74
75
76char *
77dupstr(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
89void
90readunits(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
189void
190initializeunit(struct unittype * theunit)
191{
192	theunit->factor = 1.0;
193	theunit->numerator[0] = theunit->denominator[0] = NULL;
194}
195
196
197int
198addsubunit(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
214void
215showunit(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
260void
261zeroerror(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
273int
274addunit(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
361int
362compare(const void *item1, const void *item2)
363{
364	return strcmp(*(const char **) item1, *(const char **) item2);
365}
366
367
368void
369sortunit(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
381void
382cancelunit(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
414static char buffer[100];	/* buffer for lookupunit answers with
415				   prefixes */
416
417char *
418lookupunit(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
489int
490reduceproduct(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
532int
533reduceunit(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
547int
548compareproducts(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
570int
571compareunits(struct unittype * first, struct unittype * second)
572{
573	return
574	compareproducts(first->numerator, second->numerator) ||
575	compareproducts(first->denominator, second->denominator);
576}
577
578
579int
580completereduce(struct unittype * unit)
581{
582	if (reduceunit(unit))
583		return 1;
584	sortunit(unit);
585	cancelunit(unit);
586	return 0;
587}
588
589
590void
591showanswer(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
604void
605usage(void)
606{
607	fprintf(stderr,
608		"usage: units [-f unitsfile] [-q] [-v] [from-unit to-unit]\n");
609	exit(3);
610}
611
612
613int
614main(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