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