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