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 #ifdef MSDOS 67 #define SEPERATOR ";" 68 #else 69 #define SEPARATOR ":" 70 #endif 71 72 int unitcount; 73 int prefixcount; 74 75 76 char * 77 dupstr(char *str) 78 { 79 char *ret; 80 81 ret = malloc(strlen(str) + 1); 82 if (!ret) 83 errx(3, "memory allocation error"); 84 strcpy(ret, str); 85 return (ret); 86 } 87 88 89 void 90 readerror(int linenum) 91 { 92 warnx("error in units file '%s' line %d", UNITSFILE, linenum); 93 } 94 95 96 void 97 readunits(char *userfile) 98 { 99 FILE *unitfile; 100 char line[80], *lineptr; 101 int len, linenum, i; 102 103 unitcount = 0; 104 linenum = 0; 105 106 if (userfile) { 107 unitfile = fopen(userfile, "rt"); 108 if (!unitfile) 109 errx(1, "unable to open units file '%s'", userfile); 110 } 111 else { 112 unitfile = fopen(UNITSFILE, "rt"); 113 if (!unitfile) { 114 char *direc, *env; 115 char filename[1000]; 116 117 env = getenv("PATH"); 118 if (env) { 119 direc = strtok(env, SEPARATOR); 120 while (direc) { 121 snprintf(filename, sizeof(filename), 122 "%s/%s", direc, UNITSFILE); 123 unitfile = fopen(filename, "rt"); 124 if (unitfile) 125 break; 126 direc = strtok(NULL, SEPARATOR); 127 } 128 } 129 if (!unitfile) 130 errx(1, "can't find units file '%s'", UNITSFILE); 131 } 132 } 133 while (!feof(unitfile)) { 134 if (!fgets(line, 79, unitfile)) 135 break; 136 linenum++; 137 lineptr = line; 138 if (*lineptr == '/') 139 continue; 140 lineptr += strspn(lineptr, " \n\t"); 141 len = strcspn(lineptr, " \n\t"); 142 lineptr[len] = 0; 143 if (!strlen(lineptr)) 144 continue; 145 if (lineptr[strlen(lineptr) - 1] == '-') { /* it's a prefix */ 146 if (prefixcount == MAXPREFIXES) { 147 warnx("memory for prefixes exceeded in line %d", linenum); 148 continue; 149 } 150 lineptr[strlen(lineptr) - 1] = 0; 151 prefixtable[prefixcount].prefixname = dupstr(lineptr); 152 for (i = 0; i < prefixcount; i++) 153 if (!strcmp(prefixtable[i].prefixname, lineptr)) { 154 warnx("redefinition of prefix '%s' on line %d ignored", 155 lineptr, linenum); 156 continue; 157 } 158 lineptr += len + 1; 159 if (!strlen(lineptr)) { 160 readerror(linenum); 161 continue; 162 } 163 lineptr += strspn(lineptr, " \n\t"); 164 len = strcspn(lineptr, "\n\t"); 165 lineptr[len] = 0; 166 prefixtable[prefixcount++].prefixval = dupstr(lineptr); 167 } 168 else { /* it's not a prefix */ 169 if (unitcount == MAXUNITS) { 170 warnx("memory for units exceeded in line %d", linenum); 171 continue; 172 } 173 unittable[unitcount].uname = dupstr(lineptr); 174 for (i = 0; i < unitcount; i++) 175 if (!strcmp(unittable[i].uname, lineptr)) { 176 warnx("redefinition of unit '%s' on line %d ignored", 177 lineptr, linenum); 178 continue; 179 } 180 lineptr += len + 1; 181 lineptr += strspn(lineptr, " \n\t"); 182 if (!strlen(lineptr)) { 183 readerror(linenum); 184 continue; 185 } 186 len = strcspn(lineptr, "\n\t"); 187 lineptr[len] = 0; 188 unittable[unitcount++].uval = dupstr(lineptr); 189 } 190 } 191 fclose(unitfile); 192 } 193 194 void 195 initializeunit(struct unittype * theunit) 196 { 197 theunit->factor = 1.0; 198 theunit->numerator[0] = theunit->denominator[0] = NULL; 199 } 200 201 202 int 203 addsubunit(char *product[], char *toadd) 204 { 205 char **ptr; 206 207 for (ptr = product; *ptr && *ptr != NULLUNIT; ptr++); 208 if (ptr >= product + MAXSUBUNITS) { 209 warnx("memory overflow in unit reduction"); 210 return 1; 211 } 212 if (!*ptr) 213 *(ptr + 1) = 0; 214 *ptr = dupstr(toadd); 215 return 0; 216 } 217 218 219 void 220 showunit(struct unittype * theunit) 221 { 222 char **ptr; 223 int printedslash; 224 int counter = 1; 225 226 printf("\t%.8g", theunit->factor); 227 for (ptr = theunit->numerator; *ptr; ptr++) { 228 if (ptr > theunit->numerator && **ptr && 229 !strcmp(*ptr, *(ptr - 1))) 230 counter++; 231 else { 232 if (counter > 1) 233 printf("%s%d", powerstring, counter); 234 if (**ptr) 235 printf(" %s", *ptr); 236 counter = 1; 237 } 238 } 239 if (counter > 1) 240 printf("%s%d", powerstring, counter); 241 counter = 1; 242 printedslash = 0; 243 for (ptr = theunit->denominator; *ptr; ptr++) { 244 if (ptr > theunit->denominator && **ptr && 245 !strcmp(*ptr, *(ptr - 1))) 246 counter++; 247 else { 248 if (counter > 1) 249 printf("%s%d", powerstring, counter); 250 if (**ptr) { 251 if (!printedslash) 252 printf(" /"); 253 printedslash = 1; 254 printf(" %s", *ptr); 255 } 256 counter = 1; 257 } 258 } 259 if (counter > 1) 260 printf("%s%d", powerstring, counter); 261 printf("\n"); 262 } 263 264 265 void 266 zeroerror() 267 { 268 warnx("unit reduces to zero"); 269 } 270 271 /* 272 Adds the specified string to the unit. 273 Flip is 0 for adding normally, 1 for adding reciprocal. 274 275 Returns 0 for successful addition, nonzero on error. 276 */ 277 278 int 279 addunit(struct unittype * theunit, char *toadd, int flip) 280 { 281 char *scratch, *savescr; 282 char *item; 283 char *divider, *slash; 284 int doingtop; 285 286 if (!strlen(toadd)) 287 return 1; 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 strlcpy(buffer, copy, sizeof(buffer)); 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 strlcpy(buffer, copy, sizeof(buffer)); 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 strlcpy(buffer, copy, sizeof(buffer)); 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 snprintf(buffer, sizeof(buffer), "%s %s", 473 prefixtable[i].prefixval, unit); 474 return buffer; 475 } 476 } 477 } 478 return 0; 479 } 480 481 482 483 /* 484 reduces a product of symbolic units to primitive units. 485 The three low bits are used to return flags: 486 487 bit 0 (1) set on if reductions were performed without error. 488 bit 1 (2) set on if no reductions are performed. 489 bit 2 (4) set on if an unknown unit is discovered. 490 */ 491 492 493 #define ERROR 4 494 495 int 496 reduceproduct(struct unittype * theunit, int flip) 497 { 498 499 char *toadd; 500 char **product; 501 int didsomething = 2; 502 503 if (flip) 504 product = theunit->denominator; 505 else 506 product = theunit->numerator; 507 508 for (; *product; product++) { 509 510 for (;;) { 511 if (!strlen(*product)) 512 break; 513 toadd = lookupunit(*product); 514 if (!toadd) { 515 printf("unknown unit '%s'\n", *product); 516 return ERROR; 517 } 518 if (strchr(toadd, PRIMITIVECHAR)) 519 break; 520 didsomething = 1; 521 if (*product != NULLUNIT) { 522 free(*product); 523 *product = NULLUNIT; 524 } 525 if (addunit(theunit, toadd, flip)) 526 return ERROR; 527 } 528 } 529 return didsomething; 530 } 531 532 533 /* 534 Reduces numerator and denominator of the specified unit. 535 Returns 0 on success, or 1 on unknown unit error. 536 */ 537 538 int 539 reduceunit(struct unittype * theunit) 540 { 541 int ret; 542 543 ret = 1; 544 while (ret & 1) { 545 ret = reduceproduct(theunit, 0) | reduceproduct(theunit, 1); 546 if (ret & 4) 547 return 1; 548 } 549 return 0; 550 } 551 552 553 int 554 compareproducts(char **one, char **two) 555 { 556 while (*one || *two) { 557 if (!*one && *two != NULLUNIT) 558 return 1; 559 if (!*two && *one != NULLUNIT) 560 return 1; 561 if (*one == NULLUNIT) 562 one++; 563 else if (*two == NULLUNIT) 564 two++; 565 else if (strcmp(*one, *two)) 566 return 1; 567 else 568 one++, two++; 569 } 570 return 0; 571 } 572 573 574 /* Return zero if units are compatible, nonzero otherwise */ 575 576 int 577 compareunits(struct unittype * first, struct unittype * second) 578 { 579 return 580 compareproducts(first->numerator, second->numerator) || 581 compareproducts(first->denominator, second->denominator); 582 } 583 584 585 int 586 completereduce(struct unittype * unit) 587 { 588 if (reduceunit(unit)) 589 return 1; 590 sortunit(unit); 591 cancelunit(unit); 592 return 0; 593 } 594 595 596 void 597 showanswer(struct unittype * have, struct unittype * want) 598 { 599 if (compareunits(have, want)) { 600 printf("conformability error\n"); 601 showunit(have); 602 showunit(want); 603 } 604 else 605 printf("\t* %.8g\n\t/ %.8g\n", have->factor / want->factor, 606 want->factor / have->factor); 607 } 608 609 610 void 611 usage() 612 { 613 fprintf(stderr, 614 "usage: units [-f unitsfile] [-q] [-v] [from-unit to-unit]\n"); 615 exit(3); 616 } 617 618 619 int 620 main(int argc, char **argv) 621 { 622 623 struct unittype have, want; 624 char havestr[81], wantstr[81]; 625 int optchar; 626 char *userfile = 0; 627 int quiet = 0; 628 629 while ((optchar = getopt(argc, argv, "vqf:")) != -1) { 630 switch (optchar) { 631 case 'f': 632 userfile = optarg; 633 break; 634 case 'q': 635 quiet = 1; 636 break; 637 case 'v': 638 fprintf(stderr, "\n units version %s Copyright (c) 1993 by Adrian Mariano\n", 639 VERSION); 640 fprintf(stderr, " This program may be freely distributed\n"); 641 usage(); 642 default: 643 usage(); 644 break; 645 } 646 } 647 648 if (optind != argc - 2 && optind != argc) 649 usage(); 650 651 readunits(userfile); 652 653 if (optind == argc - 2) { 654 strlcpy(havestr, argv[optind], sizeof(havestr)); 655 strlcpy(wantstr, argv[optind + 1], sizeof(wantstr)); 656 initializeunit(&have); 657 addunit(&have, havestr, 0); 658 completereduce(&have); 659 initializeunit(&want); 660 addunit(&want, wantstr, 0); 661 completereduce(&want); 662 showanswer(&have, &want); 663 } 664 else { 665 if (!quiet) 666 printf("%d units, %d prefixes\n", unitcount, 667 prefixcount); 668 for (;;) { 669 do { 670 initializeunit(&have); 671 if (!quiet) 672 printf("You have: "); 673 if (!fgets(havestr, 80, stdin)) { 674 if (!quiet) 675 putchar('\n'); 676 exit(0); 677 } 678 } while (addunit(&have, havestr, 0) || 679 completereduce(&have)); 680 do { 681 initializeunit(&want); 682 if (!quiet) 683 printf("You want: "); 684 if (!fgets(wantstr, 80, stdin)) { 685 if (!quiet) 686 putchar('\n'); 687 exit(0); 688 } 689 } while (addunit(&want, wantstr, 0) || 690 completereduce(&want)); 691 showanswer(&have, &want); 692 } 693 } 694 695 return(0); 696 } 697