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