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