1 /* $OpenBSD: units.c,v 1.9 2003/07/02 01:57:15 deraadt 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, 79, 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 lineptr[strlen(lineptr) - 1] = 0; 175 prefixtable[prefixcount].prefixname = dupstr(lineptr); 176 for (i = 0; i < prefixcount; i++) 177 if (!strcmp(prefixtable[i].prefixname, lineptr)) { 178 fprintf(stderr, 179 "Redefinition of prefix '%s' on line %d ignored\n", 180 lineptr, linenum); 181 continue; 182 } 183 lineptr += len + 1; 184 if (!strlen(lineptr)) { 185 readerror(linenum); 186 continue; 187 } 188 lineptr += strspn(lineptr, " \n\t"); 189 len = strcspn(lineptr, "\n\t"); 190 lineptr[len] = 0; 191 prefixtable[prefixcount++].prefixval = dupstr(lineptr); 192 } else { /* it's not a prefix */ 193 if (unitcount == MAXUNITS) { 194 fprintf(stderr, 195 "Memory for units exceeded in line %d\n", 196 linenum); 197 continue; 198 } 199 unittable[unitcount].uname = dupstr(lineptr); 200 for (i = 0; i < unitcount; i++) 201 if (!strcmp(unittable[i].uname, lineptr)) { 202 fprintf(stderr, 203 "Redefinition of unit '%s' on line %d ignored\n", 204 lineptr, linenum); 205 continue; 206 } 207 lineptr += len + 1; 208 lineptr += strspn(lineptr, " \n\t"); 209 if (!strlen(lineptr)) { 210 readerror(linenum); 211 continue; 212 } 213 len = strcspn(lineptr, "\n\t"); 214 lineptr[len] = 0; 215 unittable[unitcount++].uval = dupstr(lineptr); 216 } 217 } 218 fclose(unitfile); 219 } 220 221 void 222 initializeunit(struct unittype *theunit) 223 { 224 theunit->factor = 1.0; 225 theunit->numerator[0] = theunit->denominator[0] = NULL; 226 } 227 228 229 int 230 addsubunit(char *product[], char *toadd) 231 { 232 char **ptr; 233 234 for (ptr = product; *ptr && *ptr != NULLUNIT; ptr++); 235 if (ptr >= product + MAXSUBUNITS) { 236 fprintf(stderr, "Memory overflow in unit reduction\n"); 237 return 1; 238 } 239 if (!*ptr) 240 *(ptr + 1) = 0; 241 *ptr = dupstr(toadd); 242 return 0; 243 } 244 245 246 void 247 showunit(struct unittype *theunit) 248 { 249 char **ptr; 250 int printedslash; 251 int counter = 1; 252 253 printf("\t%.8g", theunit->factor); 254 for (ptr = theunit->numerator; *ptr; ptr++) { 255 if (ptr > theunit->numerator && **ptr && 256 !strcmp(*ptr, *(ptr - 1))) 257 counter++; 258 else { 259 if (counter > 1) 260 printf("%s%d", powerstring, counter); 261 if (**ptr) 262 printf(" %s", *ptr); 263 counter = 1; 264 } 265 } 266 if (counter > 1) 267 printf("%s%d", powerstring, counter); 268 counter = 1; 269 printedslash = 0; 270 for (ptr = theunit->denominator; *ptr; ptr++) { 271 if (ptr > theunit->denominator && **ptr && 272 !strcmp(*ptr, *(ptr - 1))) 273 counter++; 274 else { 275 if (counter > 1) 276 printf("%s%d", powerstring, counter); 277 if (**ptr) { 278 if (!printedslash) 279 printf(" /"); 280 printedslash = 1; 281 printf(" %s", *ptr); 282 } 283 counter = 1; 284 } 285 } 286 if (counter > 1) 287 printf("%s%d", powerstring, counter); 288 printf("\n"); 289 } 290 291 292 void 293 zeroerror(void) 294 { 295 fprintf(stderr, "Unit reduces to zero\n"); 296 } 297 298 /* 299 Adds the specified string to the unit. 300 Flip is 0 for adding normally, 1 for adding reciprocal. 301 302 Returns 0 for successful addition, nonzero on error. 303 */ 304 305 int 306 addunit(struct unittype *theunit, char *toadd, int flip) 307 { 308 char *scratch, *savescr; 309 char *item; 310 char *divider, *slash; 311 int doingtop; 312 313 savescr = scratch = dupstr(toadd); 314 for (slash = scratch + 1; *slash; slash++) 315 if (*slash == '-' && 316 (tolower(*(slash - 1)) != 'e' || 317 !strchr(".0123456789", *(slash + 1)))) 318 *slash = ' '; 319 slash = strchr(scratch, '/'); 320 if (slash) 321 *slash = 0; 322 doingtop = 1; 323 do { 324 item = strtok(scratch, " *\t\n/"); 325 while (item) { 326 if (strchr("0123456789.", *item)) { /* item is a number */ 327 double num; 328 329 divider = strchr(item, '|'); 330 if (divider) { 331 *divider = 0; 332 num = atof(item); 333 if (!num) { 334 zeroerror(); 335 return 1; 336 } 337 if (doingtop ^ flip) 338 theunit->factor *= num; 339 else 340 theunit->factor /= num; 341 num = atof(divider + 1); 342 if (!num) { 343 zeroerror(); 344 return 1; 345 } 346 if (doingtop ^ flip) 347 theunit->factor /= num; 348 else 349 theunit->factor *= num; 350 } else { 351 num = atof(item); 352 if (!num) { 353 zeroerror(); 354 return 1; 355 } 356 if (doingtop ^ flip) 357 theunit->factor *= num; 358 else 359 theunit->factor /= num; 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 } else 380 doingtop--; 381 } while (doingtop >= 0); 382 free(savescr); 383 return 0; 384 } 385 386 387 int 388 compare(const void *item1, const void *item2) 389 { 390 return strcmp(*(char **) item1, *(char **) item2); 391 } 392 393 394 void 395 sortunit(struct unittype *theunit) 396 { 397 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 407 void 408 cancelunit(struct unittype *theunit) 409 { 410 char **den, **num; 411 int comp; 412 413 den = theunit->denominator; 414 num = theunit->numerator; 415 416 while (*num && *den) { 417 comp = strcmp(*den, *num); 418 if (!comp) { 419 #if 0 420 if (*den!=NULLUNIT) 421 free(*den); 422 if (*num!=NULLUNIT) 423 free(*num); 424 #endif 425 *den++ = NULLUNIT; 426 *num++ = NULLUNIT; 427 } else if (comp < 0) 428 den++; 429 else 430 num++; 431 } 432 } 433 434 435 436 437 /* 438 Looks up the definition for the specified unit. 439 Returns a pointer to the definition or a null pointer 440 if the specified unit does not appear in the units table. 441 */ 442 443 static char buffer[100]; /* buffer for lookupunit answers with 444 prefixes */ 445 446 char * 447 lookupunit(char *unit) 448 { 449 int i; 450 char *copy; 451 452 for (i = 0; i < unitcount; i++) { 453 if (!strcmp(unittable[i].uname, unit)) 454 return unittable[i].uval; 455 } 456 457 if (unit[strlen(unit) - 1] == '^') { 458 copy = dupstr(unit); 459 copy[strlen(copy) - 1] = '\0'; 460 for (i = 0; i < unitcount; i++) { 461 if (!strcmp(unittable[i].uname, copy)) { 462 strlcpy(buffer, copy, sizeof(buffer)); 463 free(copy); 464 return buffer; 465 } 466 } 467 free(copy); 468 } 469 if (unit[strlen(unit) - 1] == 's') { 470 copy = dupstr(unit); 471 copy[strlen(copy) - 1] = 0; 472 for (i = 0; i < unitcount; i++) { 473 if (!strcmp(unittable[i].uname, copy)) { 474 strlcpy(buffer, copy, sizeof(buffer)); 475 free(copy); 476 return buffer; 477 } 478 } 479 if (copy[strlen(copy) - 1] == 'e') { 480 copy[strlen(copy) - 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 } 489 free(copy); 490 } 491 for (i = 0; i < prefixcount; i++) { 492 if (!strncmp(prefixtable[i].prefixname, unit, 493 strlen(prefixtable[i].prefixname))) { 494 unit += strlen(prefixtable[i].prefixname); 495 if (!strlen(unit) || lookupunit(unit)) { 496 snprintf(buffer, sizeof(buffer), 497 "%s %s", prefixtable[i].prefixval, unit); 498 return buffer; 499 } 500 } 501 } 502 return 0; 503 } 504 505 506 507 /* 508 reduces a product of symbolic units to primitive units. 509 The three low bits are used to return flags: 510 511 bit 0 (1) set on if reductions were performed without error. 512 bit 1 (2) set on if no reductions are performed. 513 bit 2 (4) set on if an unknown unit is discovered. 514 */ 515 516 517 #define ERROR 4 518 519 int 520 reduceproduct(struct unittype *theunit, int flip) 521 { 522 char *toadd, **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(*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 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 575 int 576 compareproducts(char **one, char **two) 577 { 578 while (*one || *two) { 579 if (!*one && *two != NULLUNIT) 580 return 1; 581 if (!*two && *one != NULLUNIT) 582 return 1; 583 if (*one == NULLUNIT) 584 one++; 585 else if (*two == NULLUNIT) 586 two++; 587 else if (strcmp(*one, *two)) 588 return 1; 589 else 590 one++, two++; 591 } 592 return 0; 593 } 594 595 596 /* Return zero if units are compatible, nonzero otherwise */ 597 598 int 599 compareunits(struct unittype *first, struct unittype *second) 600 { 601 return compareproducts(first->numerator, second->numerator) || 602 compareproducts(first->denominator, second->denominator); 603 } 604 605 606 int 607 completereduce(struct unittype *unit) 608 { 609 if (reduceunit(unit)) 610 return 1; 611 sortunit(unit); 612 cancelunit(unit); 613 return 0; 614 } 615 616 617 void 618 showanswer(struct unittype *have, struct unittype *want) 619 { 620 if (compareunits(have, want)) { 621 printf("conformability error\n"); 622 showunit(have); 623 showunit(want); 624 } else 625 printf("\t* %.8g\n\t/ %.8g\n", have->factor / want->factor, 626 want->factor / have->factor); 627 } 628 629 630 void 631 usage(void) 632 { 633 fprintf(stderr, "units [-f unitsfile] [-q] [-v] [from-unit to-unit]\n"); 634 fprintf(stderr, " -f specify units file\n"); 635 fprintf(stderr, " -q suppress prompting (quiet)\n"); 636 fprintf(stderr, " -v print version number\n"); 637 exit(3); 638 } 639 640 641 int 642 main(int argc, char **argv) 643 { 644 645 struct unittype have, want; 646 char havestr[81], wantstr[81]; 647 int optchar; 648 char *userfile = 0; 649 int quiet = 0; 650 651 extern char *optarg; 652 extern int optind; 653 654 while ((optchar = getopt(argc, argv, "vqf:")) != -1) { 655 switch (optchar) { 656 case 'f': 657 userfile = optarg; 658 break; 659 case 'q': 660 quiet = 1; 661 break; 662 case 'v': 663 fprintf(stderr, 664 "units version %s Copyright (c) 1993 by Adrian Mariano\n", 665 VERSION); 666 fprintf(stderr, 667 "This program may be freely distributed\n"); 668 usage(); 669 default: 670 usage(); 671 break; 672 } 673 } 674 675 if (optind != argc - 2 && optind != argc) 676 usage(); 677 678 readunits(userfile); 679 680 if (optind == argc - 2) { 681 strlcpy(havestr, argv[optind], sizeof(havestr)); 682 strlcpy(wantstr, argv[optind + 1], sizeof(wantstr)); 683 initializeunit(&have); 684 addunit(&have, havestr, 0); 685 completereduce(&have); 686 initializeunit(&want); 687 addunit(&want, wantstr, 0); 688 completereduce(&want); 689 showanswer(&have, &want); 690 } else { 691 if (!quiet) 692 printf("%d units, %d prefixes\n", unitcount, 693 prefixcount); 694 for (;;) { 695 do { 696 initializeunit(&have); 697 if (!quiet) 698 printf("You have: "); 699 if (!fgets(havestr, 80, stdin)) { 700 if (!quiet) 701 putchar('\n'); 702 exit(0); 703 } 704 } while (addunit(&have, havestr, 0) || 705 completereduce(&have)); 706 do { 707 initializeunit(&want); 708 if (!quiet) 709 printf("You want: "); 710 if (!fgets(wantstr, 80, stdin)) { 711 if (!quiet) 712 putchar('\n'); 713 exit(0); 714 } 715 } while (addunit(&want, wantstr, 0) || 716 completereduce(&want)); 717 showanswer(&have, &want); 718 } 719 } 720 return (0); 721 } 722