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