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