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