2 * units.c Copyright (c) 1993 by Adrian Mariano (adrian@cam.cornell.edu)
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
14 * I would appreciate (though I do not require) receiving a copy of any
15 * improvements you might make to this program.
19 static const char rcsid
[] =
34 #include <sys/capsicum.h>
36 #include "pathnames.h"
39 #define UNITSFILE _PATH_UNITSLIB
43 #define MAXPREFIXES 100
45 #define MAXSUBUNITS 500
47 #define PRIMITIVECHAR '!'
49 static const char *powerstring
= "^";
54 } unittable
[MAXUNITS
];
57 char *numerator
[MAXSUBUNITS
];
58 char *denominator
[MAXSUBUNITS
];
67 } prefixtable
[MAXPREFIXES
];
70 static char NULLUNIT
[] = "";
75 static int prefixcount
;
76 static bool verbose
= false;
77 static bool terse
= false;
78 static const char * outputformat
;
79 static const char * havestr
;
80 static const char * wantstr
;
82 static int addsubunit(char *product
[], char *toadd
);
83 static int addunit(struct unittype
*theunit
, const char *toadd
, int flip
, int quantity
);
84 static void cancelunit(struct unittype
* theunit
);
85 static int compare(const void *item1
, const void *item2
);
86 static int compareproducts(char **one
, char **two
);
87 static int compareunits(struct unittype
* first
, struct unittype
* second
);
88 static int completereduce(struct unittype
* unit
);
89 static char *dupstr(const char *str
);
90 static void initializeunit(struct unittype
* theunit
);
91 static char *lookupunit(const char *unit
);
92 static void readunits(const char *userfile
);
93 static int reduceproduct(struct unittype
* theunit
, int flip
);
94 static int reduceunit(struct unittype
* theunit
);
95 static void showanswer(struct unittype
* have
, struct unittype
* want
);
96 static void showunit(struct unittype
* theunit
);
97 static void sortunit(struct unittype
* theunit
);
98 static void usage(void);
99 static void zeroerror(void);
101 static const char* promptstr
= "";
103 static const char * prompt(EditLine
*e __unused
) {
108 dupstr(const char *str
)
120 readunits(const char *userfile
)
123 char line
[512], *lineptr
;
125 cap_rights_t unitfilerights
;
131 unitfile
= fopen(userfile
, "rt");
133 errx(1, "unable to open units file '%s'", userfile
);
136 unitfile
= fopen(UNITSFILE
, "rt");
141 env
= getenv("PATH");
143 direc
= strtok(env
, SEPARATOR
);
145 snprintf(filename
, sizeof(filename
),
146 "%s/%s", direc
, UNITSFILE
);
147 unitfile
= fopen(filename
, "rt");
150 direc
= strtok(NULL
, SEPARATOR
);
154 errx(1, "can't find units file '%s'", UNITSFILE
);
157 cap_rights_init(&unitfilerights
, CAP_READ
, CAP_FSTAT
);
158 if (cap_rights_limit(fileno(unitfile
), &unitfilerights
) < 0
160 err(1, "cap_rights_limit() failed");
161 while (!feof(unitfile
)) {
162 if (!fgets(line
, sizeof(line
), unitfile
))
166 if (*lineptr
== '/' || *lineptr
== '#')
168 lineptr
+= strspn(lineptr
, " \n\t");
169 len
= strcspn(lineptr
, " \n\t");
171 if (!strlen(lineptr
))
173 if (lineptr
[strlen(lineptr
) - 1] == '-') { /* it's a prefix */
174 if (prefixcount
== MAXPREFIXES
) {
175 warnx("memory for prefixes exceeded in line %d", linenum
);
178 lineptr
[strlen(lineptr
) - 1] = 0;
179 prefixtable
[prefixcount
].prefixname
= dupstr(lineptr
);
180 for (i
= 0; i
< prefixcount
; i
++)
181 if (!strcmp(prefixtable
[i
].prefixname
, lineptr
)) {
182 warnx("redefinition of prefix '%s' on line %d ignored",
187 lineptr
+= strspn(lineptr
, " \n\t");
188 len
= strcspn(lineptr
, "\n\t");
190 warnx("unexpected end of prefix on line %d",
195 prefixtable
[prefixcount
++].prefixval
= dupstr(lineptr
);
197 else { /* it's not a prefix */
198 if (unitcount
== MAXUNITS
) {
199 warnx("memory for units exceeded in line %d", linenum
);
202 unittable
[unitcount
].uname
= dupstr(lineptr
);
203 for (i
= 0; i
< unitcount
; i
++)
204 if (!strcmp(unittable
[i
].uname
, lineptr
)) {
205 warnx("redefinition of unit '%s' on line %d ignored",
210 lineptr
+= strspn(lineptr
, " \n\t");
211 if (!strlen(lineptr
)) {
212 warnx("unexpected end of unit on line %d",
216 len
= strcspn(lineptr
, "\n\t");
218 unittable
[unitcount
++].uval
= dupstr(lineptr
);
225 initializeunit(struct unittype
* theunit
)
227 theunit
->numerator
[0] = theunit
->denominator
[0] = NULL
;
228 theunit
->factor
= 1.0;
229 theunit
->offset
= 0.0;
230 theunit
->quantity
= 0;
235 addsubunit(char *product
[], char *toadd
)
239 for (ptr
= product
; *ptr
&& *ptr
!= NULLUNIT
; ptr
++);
240 if (ptr
>= product
+ MAXSUBUNITS
) {
241 warnx("memory overflow in unit reduction");
246 *ptr
= dupstr(toadd
);
252 showunit(struct unittype
* theunit
)
258 printf("%.8g", theunit
->factor
);
260 printf("&%.8g", theunit
->offset
);
261 for (ptr
= theunit
->numerator
; *ptr
; ptr
++) {
262 if (ptr
> theunit
->numerator
&& **ptr
&&
263 !strcmp(*ptr
, *(ptr
- 1)))
267 printf("%s%d", powerstring
, counter
);
274 printf("%s%d", powerstring
, counter
);
277 for (ptr
= theunit
->denominator
; *ptr
; ptr
++) {
278 if (ptr
> theunit
->denominator
&& **ptr
&&
279 !strcmp(*ptr
, *(ptr
- 1)))
283 printf("%s%d", powerstring
, counter
);
294 printf("%s%d", powerstring
, counter
);
302 warnx("unit reduces to zero");
306 Adds the specified string to the unit.
307 Flip is 0 for adding normally, 1 for adding reciprocal.
308 Quantity is 1 if this is a quantity to be converted rather than a pure unit.
310 Returns 0 for successful addition, nonzero on error.
314 addunit(struct unittype
* theunit
, const char *toadd
, int flip
, int quantity
)
316 char *scratch
, *savescr
;
318 char *divider
, *slash
, *offset
;
324 savescr
= scratch
= dupstr(toadd
);
325 for (slash
= scratch
+ 1; *slash
; slash
++)
327 (tolower(*(slash
- 1)) != 'e' ||
328 !strchr(".0123456789", *(slash
+ 1))))
330 slash
= strchr(scratch
, '/');
335 item
= strtok(scratch
, " *\t\n/");
337 if (strchr("0123456789.", *item
)) { /* item is a number */
338 double num
, offsetnum
;
341 theunit
->quantity
= 1;
343 offset
= strchr(item
, '&');
346 offsetnum
= atof(offset
+1);
350 divider
= strchr(item
, '|');
358 if (doingtop
^ flip
) {
359 theunit
->factor
*= num
;
360 theunit
->offset
*= num
;
362 theunit
->factor
/= num
;
363 theunit
->offset
/= num
;
365 num
= atof(divider
+ 1);
370 if (doingtop
^ flip
) {
371 theunit
->factor
/= num
;
372 theunit
->offset
/= num
;
374 theunit
->factor
*= num
;
375 theunit
->offset
*= num
;
384 if (doingtop
^ flip
) {
385 theunit
->factor
*= num
;
386 theunit
->offset
*= num
;
388 theunit
->factor
/= num
;
389 theunit
->offset
/= num
;
393 theunit
->offset
+= offsetnum
;
395 else { /* item is not a number */
398 if (strchr("23456789",
399 item
[strlen(item
) - 1])) {
400 repeat
= item
[strlen(item
) - 1] - '0';
401 item
[strlen(item
) - 1] = 0;
403 for (; repeat
; repeat
--)
404 if (addsubunit(doingtop
^ flip
? theunit
->numerator
: theunit
->denominator
, item
))
407 item
= strtok(NULL
, " *\t/\n");
415 } while (doingtop
>= 0);
422 compare(const void *item1
, const void *item2
)
424 return strcmp(*(const char * const *)item1
, *(const char * const *)item2
);
429 sortunit(struct unittype
* theunit
)
434 for (count
= 0, ptr
= theunit
->numerator
; *ptr
; ptr
++, count
++);
435 qsort(theunit
->numerator
, count
, sizeof(char *), compare
);
436 for (count
= 0, ptr
= theunit
->denominator
; *ptr
; ptr
++, count
++);
437 qsort(theunit
->denominator
, count
, sizeof(char *), compare
);
442 cancelunit(struct unittype
* theunit
)
447 den
= theunit
->denominator
;
448 num
= theunit
->numerator
;
450 while (*num
&& *den
) {
451 comp
= strcmp(*den
, *num
);
453 /* if (*den!=NULLUNIT) free(*den);
454 if (*num!=NULLUNIT) free(*num);*/
469 Looks up the definition for the specified unit.
470 Returns a pointer to the definition or a null pointer
471 if the specified unit does not appear in the units table.
474 static char buffer
[100]; /* buffer for lookupunit answers with
478 lookupunit(const char *unit
)
483 for (i
= 0; i
< unitcount
; i
++) {
484 if (!strcmp(unittable
[i
].uname
, unit
))
485 return unittable
[i
].uval
;
488 if (unit
[strlen(unit
) - 1] == '^') {
490 copy
[strlen(copy
) - 1] = 0;
491 for (i
= 0; i
< unitcount
; i
++) {
492 if (!strcmp(unittable
[i
].uname
, copy
)) {
493 strlcpy(buffer
, copy
, sizeof(buffer
));
500 if (unit
[strlen(unit
) - 1] == 's') {
502 copy
[strlen(copy
) - 1] = 0;
503 for (i
= 0; i
< unitcount
; i
++) {
504 if (!strcmp(unittable
[i
].uname
, copy
)) {
505 strlcpy(buffer
, copy
, sizeof(buffer
));
510 if (copy
[strlen(copy
) - 1] == 'e') {
511 copy
[strlen(copy
) - 1] = 0;
512 for (i
= 0; i
< unitcount
; i
++) {
513 if (!strcmp(unittable
[i
].uname
, copy
)) {
514 strlcpy(buffer
, copy
, sizeof(buffer
));
522 for (i
= 0; i
< prefixcount
; i
++) {
523 size_t len
= strlen(prefixtable
[i
].prefixname
);
524 if (!strncmp(prefixtable
[i
].prefixname
, unit
, len
)) {
525 if (!strlen(unit
+ len
) || lookupunit(unit
+ len
)) {
526 snprintf(buffer
, sizeof(buffer
), "%s %s",
527 prefixtable
[i
].prefixval
, unit
+ len
);
538 reduces a product of symbolic units to primitive units.
539 The three low bits are used to return flags:
541 bit 0 (1) set on if reductions were performed without error.
542 bit 1 (2) set on if no reductions are performed.
543 bit 2 (4) set on if an unknown unit is discovered.
550 reduceproduct(struct unittype
* theunit
, int flip
)
555 int didsomething
= 2;
558 product
= theunit
->denominator
;
560 product
= theunit
->numerator
;
562 for (; *product
; product
++) {
565 if (!strlen(*product
))
567 toadd
= lookupunit(*product
);
569 printf("unknown unit '%s'\n", *product
);
572 if (strchr(toadd
, PRIMITIVECHAR
))
575 if (*product
!= NULLUNIT
) {
579 if (addunit(theunit
, toadd
, flip
, 0))
588 Reduces numerator and denominator of the specified unit.
589 Returns 0 on success, or 1 on unknown unit error.
593 reduceunit(struct unittype
* theunit
)
599 ret
= reduceproduct(theunit
, 0) | reduceproduct(theunit
, 1);
608 compareproducts(char **one
, char **two
)
610 while (*one
|| *two
) {
611 if (!*one
&& *two
!= NULLUNIT
)
613 if (!*two
&& *one
!= NULLUNIT
)
615 if (*one
== NULLUNIT
)
617 else if (*two
== NULLUNIT
)
619 else if (strcmp(*one
, *two
))
628 /* Return zero if units are compatible, nonzero otherwise */
631 compareunits(struct unittype
* first
, struct unittype
* second
)
634 compareproducts(first
->numerator
, second
->numerator
) ||
635 compareproducts(first
->denominator
, second
->denominator
);
640 completereduce(struct unittype
* unit
)
642 if (reduceunit(unit
))
650 showanswer(struct unittype
* have
, struct unittype
* want
)
655 if (compareunits(have
, want
)) {
656 printf("conformability error\n");
658 printf("\t%s = ", havestr
);
664 printf("\t%s = ", wantstr
);
670 else if (have
->offset
!= want
->offset
) {
672 printf("WARNING: conversion of non-proportional quantities.\n");
673 if (have
->quantity
) {
674 asprintf(&oformat
, "\t%s\n", outputformat
);
676 (have
->factor
+ have
->offset
-want
->offset
)/want
->factor
);
680 asprintf(&oformat
, "\t (-> x*%sg %sg)\n\t (<- y*%sg %sg)\n",
681 outputformat
, outputformat
, outputformat
, outputformat
);
683 have
->factor
/ want
->factor
,
684 (have
->offset
-want
->offset
)/want
->factor
,
685 want
->factor
/ have
->factor
,
686 (want
->offset
- have
->offset
)/have
->factor
);
690 ans
= have
->factor
/ want
->factor
;
693 printf("\t%s = ", havestr
);
694 printf(outputformat
, ans
);
695 printf(" * %s", wantstr
);
699 printf(outputformat
, ans
);
704 printf(outputformat
, ans
);
709 printf("\t%s = (1 / ", havestr
);
710 printf(outputformat
, 1/ans
);
711 printf(") * %s\n", wantstr
);
715 printf(outputformat
, 1/ans
);
726 "usage: units [-f unitsfile] [-UVq] [from-unit to-unit]\n");
730 static struct option longopts
[] = {
731 {"help", no_argument
, NULL
, 'h'},
732 {"exponential", no_argument
, NULL
, 'e'},
733 {"file", required_argument
, NULL
, 'f'},
734 {"output-format", required_argument
, NULL
, 'o'},
735 {"quiet", no_argument
, NULL
, 'q'},
736 {"terse", no_argument
, NULL
, 't'},
737 {"unitsfile", no_argument
, NULL
, 'U'},
738 {"verbose", no_argument
, NULL
, 'v'},
739 {"version", no_argument
, NULL
, 'V'},
745 main(int argc
, char **argv
)
748 struct unittype have
, want
;
759 outputformat
= "%.8g";
760 while ((optchar
= getopt_long(argc
, argv
, "+ehf:oqtvUV", longopts
, NULL
)) != -1) {
763 outputformat
= "%6e";
767 if (strlen(optarg
) == 0)
779 outputformat
= optarg
;
785 fprintf(stderr
, "FreeBSD units\n");
788 if (access(UNITSFILE
, F_OK
) == 0)
789 printf("%s\n", UNITSFILE
);
791 printf("Units data file not found");
805 if (optind
== argc
- 2) {
806 if (cap_enter() < 0 && errno
!= ENOSYS
)
807 err(1, "unable to enter capability mode");
809 havestr
= argv
[optind
];
810 wantstr
= argv
[optind
+ 1];
811 initializeunit(&have
);
812 addunit(&have
, havestr
, 0, 1);
813 completereduce(&have
);
814 initializeunit(&want
);
815 addunit(&want
, wantstr
, 0, 1);
816 completereduce(&want
);
817 showanswer(&have
, &want
);
819 inhistory
= history_init();
820 el
= el_init(argv
[0], stdin
, stdout
, stderr
);
821 el_set(el
, EL_PROMPT
, &prompt
);
822 el_set(el
, EL_EDITOR
, "emacs");
823 el_set(el
, EL_SIGNAL
, 1);
824 el_set(el
, EL_HIST
, history
, inhistory
);
826 history(inhistory
, &ev
, H_SETSIZE
, 800);
828 err(1, "Could not initialize history");
830 if (cap_enter() < 0 && errno
!= ENOSYS
)
831 err(1, "unable to enter capability mode");
834 printf("%d units, %d prefixes\n", unitcount
,
838 initializeunit(&have
);
840 promptstr
= "You have: ";
841 havestr
= el_gets(el
, &inputsz
);
845 history(inhistory
, &ev
, H_ENTER
,
847 } while (addunit(&have
, havestr
, 0, 1) ||
848 completereduce(&have
));
850 initializeunit(&want
);
852 promptstr
= "You want: ";
853 wantstr
= el_gets(el
, &inputsz
);
857 history(inhistory
, &ev
, H_ENTER
,
859 } while (addunit(&want
, wantstr
, 0, 1) ||
860 completereduce(&want
));
861 showanswer(&have
, &want
);
864 history_end(inhistory
);