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