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