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