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