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