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