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