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