1 /* $NetBSD: units.c,v 1.11 2002/08/31 07:26:17 kristerw 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 char *powerstring = "^"; 43 44 struct { 45 char *uname; 46 char *uval; 47 } unittable[MAXUNITS]; 48 49 struct unittype { 50 char *numerator[MAXSUBUNITS]; 51 char *denominator[MAXSUBUNITS]; 52 double factor; 53 }; 54 55 struct { 56 char *prefixname; 57 char *prefixval; 58 } prefixtable[MAXPREFIXES]; 59 60 61 char *NULLUNIT = ""; 62 63 int unitcount; 64 int prefixcount; 65 66 67 int addsubunit __P((char *[], char *)); 68 int addunit __P((struct unittype *, char *, int)); 69 void cancelunit __P((struct unittype *)); 70 int compare __P((const void *, const void *)); 71 int compareproducts __P((char **, char **)); 72 int compareunits __P((struct unittype *, struct unittype *)); 73 int completereduce __P((struct unittype *)); 74 void initializeunit __P((struct unittype *)); 75 int main __P((int, char **)); 76 void readerror __P((int)); 77 void readunits __P((char *)); 78 int reduceproduct __P((struct unittype *, int)); 79 int reduceunit __P((struct unittype *)); 80 void showanswer __P((struct unittype *, struct unittype *)); 81 void showunit __P((struct unittype *)); 82 void sortunit __P((struct unittype *)); 83 void usage __P((void)); 84 void zeroerror __P((void)); 85 char *dupstr __P((char *)); 86 char *lookupunit __P((char *)); 87 88 89 char * 90 dupstr(char *str) 91 { 92 char *ret; 93 94 ret = malloc(strlen(str) + 1); 95 if (!ret) 96 err(3, "Memory allocation error"); 97 strcpy(ret, str); 98 return (ret); 99 } 100 101 102 void 103 readerror(int linenum) 104 { 105 warnx("Error in units file '%s' line %d", UNITSFILE, linenum); 106 } 107 108 109 void 110 readunits(char *userfile) 111 { 112 FILE *unitfile; 113 char line[80], *lineptr; 114 int len, linenum, i; 115 116 unitcount = 0; 117 linenum = 0; 118 119 if (userfile) { 120 unitfile = fopen(userfile, "rt"); 121 if (!unitfile) 122 err(1, "Unable to open units file '%s'", userfile); 123 } 124 else { 125 unitfile = fopen(UNITSFILE, "rt"); 126 if (!unitfile) { 127 char *direc, *env; 128 char filename[1000]; 129 char separator[2]; 130 131 env = getenv("PATH"); 132 if (env) { 133 if (strchr(env, ';')) 134 strcpy(separator, ";"); 135 else 136 strcpy(separator, ":"); 137 direc = strtok(env, separator); 138 while (direc) { 139 strcpy(filename, ""); 140 strncat(filename, direc, 999); 141 strncat(filename, "/", 142 999 - strlen(filename)); 143 strncat(filename, UNITSFILE, 144 999 - strlen(filename)); 145 unitfile = fopen(filename, "rt"); 146 if (unitfile) 147 break; 148 direc = strtok(NULL, separator); 149 } 150 } 151 if (!unitfile) 152 errx(1, "Can't find units file '%s'", 153 UNITSFILE); 154 } 155 } 156 while (!feof(unitfile)) { 157 if (!fgets(line, 79, unitfile)) 158 break; 159 linenum++; 160 lineptr = line; 161 if (*lineptr == '/') 162 continue; 163 lineptr += strspn(lineptr, " \n\t"); 164 len = strcspn(lineptr, " \n\t"); 165 lineptr[len] = 0; 166 if (!strlen(lineptr)) 167 continue; 168 if (lineptr[strlen(lineptr) - 1] == '-') { /* it's a prefix */ 169 if (prefixcount == MAXPREFIXES) { 170 warnx("Memory for prefixes exceeded in line %d", 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 warnx( 179 "Redefinition of prefix '%s' on line %d ignored", 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 } 193 else { /* it's not a prefix */ 194 if (unitcount == MAXUNITS) { 195 warnx("Memory for units exceeded in line %d", 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 warnx( 203 "Redefinition of unit '%s' on line %d ignored", 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 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 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() 294 { 295 warnx("Unit reduces to zero"); 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 } 351 else { 352 num = atof(item); 353 if (!num) { 354 zeroerror(); 355 return 1; 356 } 357 if (doingtop ^ flip) 358 theunit->factor *= num; 359 else 360 theunit->factor /= num; 361 362 } 363 } 364 else { /* item is not a number */ 365 int repeat = 1; 366 367 if (strchr("23456789", 368 item[strlen(item) - 1])) { 369 repeat = item[strlen(item) - 1] - '0'; 370 item[strlen(item) - 1] = 0; 371 } 372 for (; repeat; repeat--) 373 if (addsubunit(doingtop ^ flip ? theunit->numerator : theunit->denominator, item)) 374 return 1; 375 } 376 item = strtok(NULL, " *\t/\n"); 377 } 378 doingtop--; 379 if (slash) { 380 scratch = slash + 1; 381 } 382 else 383 doingtop--; 384 } while (doingtop >= 0); 385 free(savescr); 386 return 0; 387 } 388 389 390 int 391 compare(const void *item1, const void *item2) 392 { 393 return strcmp(*(char **) item1, *(char **) item2); 394 } 395 396 397 void 398 sortunit(struct unittype * theunit) 399 { 400 char **ptr; 401 int count; 402 403 for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++); 404 qsort(theunit->numerator, count, sizeof(char *), compare); 405 for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++); 406 qsort(theunit->denominator, count, sizeof(char *), compare); 407 } 408 409 410 void 411 cancelunit(struct unittype * theunit) 412 { 413 char **den, **num; 414 int comp; 415 416 den = theunit->denominator; 417 num = theunit->numerator; 418 419 while (*num && *den) { 420 comp = strcmp(*den, *num); 421 if (!comp) { 422 /* if (*den!=NULLUNIT) free(*den); 423 if (*num!=NULLUNIT) free(*num);*/ 424 *den++ = NULLUNIT; 425 *num++ = NULLUNIT; 426 } 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 strcpy(buffer, copy); 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 strcpy(buffer, copy); 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 strcpy(buffer, copy); 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 strcpy(buffer, prefixtable[i].prefixval); 497 strcat(buffer, " "); 498 strcat(buffer, unit); 499 return buffer; 500 } 501 } 502 } 503 return 0; 504 } 505 506 507 508 /* 509 reduces a product of symbolic units to primitive units. 510 The three low bits are used to return flags: 511 512 bit 0 (1) set on if reductions were performed without error. 513 bit 1 (2) set on if no reductions are performed. 514 bit 2 (4) set on if an unknown unit is discovered. 515 */ 516 517 518 #define ERROR 4 519 520 int 521 reduceproduct(struct unittype * theunit, int flip) 522 { 523 524 char *toadd; 525 char **product; 526 int didsomething = 2; 527 528 if (flip) 529 product = theunit->denominator; 530 else 531 product = theunit->numerator; 532 533 for (; *product; product++) { 534 535 for (;;) { 536 if (!strlen(*product)) 537 break; 538 toadd = lookupunit(*product); 539 if (!toadd) { 540 printf("unknown unit '%s'\n", *product); 541 return ERROR; 542 } 543 if (strchr(toadd, PRIMITIVECHAR)) 544 break; 545 didsomething = 1; 546 if (*product != NULLUNIT) { 547 free(*product); 548 *product = NULLUNIT; 549 } 550 if (addunit(theunit, toadd, flip)) 551 return ERROR; 552 } 553 } 554 return didsomething; 555 } 556 557 558 /* 559 Reduces numerator and denominator of the specified unit. 560 Returns 0 on success, or 1 on unknown unit error. 561 */ 562 563 int 564 reduceunit(struct unittype * theunit) 565 { 566 int ret; 567 568 ret = 1; 569 while (ret & 1) { 570 ret = reduceproduct(theunit, 0) | reduceproduct(theunit, 1); 571 if (ret & 4) 572 return 1; 573 } 574 return 0; 575 } 576 577 578 int 579 compareproducts(char **one, char **two) 580 { 581 while (*one || *two) { 582 if (!*one && *two != NULLUNIT) 583 return 1; 584 if (!*two && *one != NULLUNIT) 585 return 1; 586 if (*one == NULLUNIT) 587 one++; 588 else if (*two == NULLUNIT) 589 two++; 590 else if (strcmp(*one, *two)) 591 return 1; 592 else 593 one++, two++; 594 } 595 return 0; 596 } 597 598 599 /* Return zero if units are compatible, nonzero otherwise */ 600 601 int 602 compareunits(struct unittype * first, struct unittype * second) 603 { 604 return 605 compareproducts(first->numerator, second->numerator) || 606 compareproducts(first->denominator, second->denominator); 607 } 608 609 610 int 611 completereduce(struct unittype * unit) 612 { 613 if (reduceunit(unit)) 614 return 1; 615 sortunit(unit); 616 cancelunit(unit); 617 return 0; 618 } 619 620 621 void 622 showanswer(struct unittype * have, struct unittype * want) 623 { 624 if (compareunits(have, want)) { 625 printf("conformability error\n"); 626 showunit(have); 627 showunit(want); 628 } 629 else 630 printf("\t* %.8g\n\t/ %.8g\n", have->factor / want->factor, 631 want->factor / have->factor); 632 } 633 634 635 void 636 usage() 637 { 638 fprintf(stderr, 639 "\nunits [-f unitsfile] [-q] [-v] [from-unit to-unit]\n"); 640 fprintf(stderr, "\n -f specify units file\n"); 641 fprintf(stderr, " -q suppress prompting (quiet)\n"); 642 fprintf(stderr, " -v print version number\n"); 643 exit(3); 644 } 645 646 647 int 648 main(int argc, char **argv) 649 { 650 651 struct unittype have, want; 652 char havestr[81], wantstr[81]; 653 int optchar; 654 char *userfile = 0; 655 int quiet = 0; 656 657 while ((optchar = getopt(argc, argv, "vqf:")) != -1) { 658 switch (optchar) { 659 case 'f': 660 userfile = optarg; 661 break; 662 case 'q': 663 quiet = 1; 664 break; 665 case 'v': 666 fprintf(stderr, "\n units version %s Copyright (c) 1993 by Adrian Mariano\n", 667 VERSION); 668 fprintf(stderr, " This program may be freely distributed\n"); 669 usage(); 670 default: 671 usage(); 672 break; 673 } 674 } 675 676 argc -= optind; 677 argv += optind; 678 679 if (argc != 3 && argc != 2 && argc != 0) 680 usage(); 681 682 readunits(userfile); 683 684 if (argc == 3) { 685 strcpy(havestr, argv[0]); 686 strcat(havestr, " "); 687 strcat(havestr, argv[1]); 688 argc--; 689 argv++; 690 argv[0] = havestr; 691 } 692 693 if (argc == 2) { 694 strcpy(havestr, argv[0]); 695 strcpy(wantstr, argv[1]); 696 initializeunit(&have); 697 addunit(&have, havestr, 0); 698 completereduce(&have); 699 initializeunit(&want); 700 addunit(&want, wantstr, 0); 701 completereduce(&want); 702 showanswer(&have, &want); 703 } 704 else { 705 if (!quiet) 706 printf("%d units, %d prefixes\n\n", unitcount, 707 prefixcount); 708 for (;;) { 709 do { 710 initializeunit(&have); 711 if (!quiet) 712 printf("You have: "); 713 if (!fgets(havestr, 80, stdin)) { 714 if (!quiet) 715 putchar('\n'); 716 exit(0); 717 } 718 } while (addunit(&have, havestr, 0) || 719 completereduce(&have)); 720 do { 721 initializeunit(&want); 722 if (!quiet) 723 printf("You want: "); 724 if (!fgets(wantstr, 80, stdin)) { 725 if (!quiet) 726 putchar('\n'); 727 exit(0); 728 } 729 } while (addunit(&want, wantstr, 0) || 730 completereduce(&want)); 731 showanswer(&have, &want); 732 } 733 } 734 return (0); 735 } 736