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