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