1 /* $OpenBSD: units.c,v 1.17 2011/10/07 20:07:25 jmc 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 #define UNITSFILE "/usr/share/misc/units.lib" 27 28 #define VERSION "1.0" 29 30 #define MAXUNITS 1000 31 #define MAXPREFIXES 100 32 33 #define MAXSUBUNITS 500 34 35 #define PRIMITIVECHAR '!' 36 37 char *powerstring = "^"; 38 39 struct { 40 char *uname; 41 char *uval; 42 } unittable[MAXUNITS]; 43 44 struct unittype { 45 char *numerator[MAXSUBUNITS]; 46 char *denominator[MAXSUBUNITS]; 47 double factor; 48 }; 49 50 struct { 51 char *prefixname; 52 char *prefixval; 53 } prefixtable[MAXPREFIXES]; 54 55 56 char *NULLUNIT = ""; 57 58 int unitcount; 59 int prefixcount; 60 61 char *dupstr(char *); 62 void readunits(char *); 63 void initializeunit(struct unittype *); 64 int addsubunit(char *[], char *); 65 void showunit(struct unittype *); 66 void zeroerror(void); 67 int addunit(struct unittype *, char *, int); 68 int compare(const void *, const void *); 69 void sortunit(struct unittype *); 70 void cancelunit(struct unittype *); 71 char *lookupunit(char *); 72 int reduceproduct(struct unittype *, int); 73 int reduceunit(struct unittype *); 74 int compareproducts(char **, char **); 75 int compareunits(struct unittype *, struct unittype *); 76 int completereduce(struct unittype *); 77 void showanswer(struct unittype *, struct unittype *); 78 void usage(void); 79 80 char * 81 dupstr(char *str) 82 { 83 char *ret; 84 85 ret = strdup(str); 86 if (!ret) { 87 fprintf(stderr, "Memory allocation error\n"); 88 exit(3); 89 } 90 return (ret); 91 } 92 93 94 void 95 readunits(char *userfile) 96 { 97 char line[512], *lineptr; 98 int len, linenum, i; 99 FILE *unitfile; 100 101 unitcount = 0; 102 linenum = 0; 103 104 if (userfile) { 105 unitfile = fopen(userfile, "r"); 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, "r"); 113 if (!unitfile) { 114 fprintf(stderr, "Can't find units file '%s'\n", 115 UNITSFILE); 116 exit(1); 117 } 118 } 119 while (!feof(unitfile)) { 120 if (!fgets(line, sizeof(line), unitfile)) 121 break; 122 linenum++; 123 lineptr = line; 124 if (*lineptr == '/') 125 continue; 126 lineptr += strspn(lineptr, " \n\t"); 127 len = strcspn(lineptr, " \n\t"); 128 lineptr[len] = 0; 129 if (!strlen(lineptr)) 130 continue; 131 if (lineptr[strlen(lineptr) - 1] == '-') { /* it's a prefix */ 132 if (prefixcount == MAXPREFIXES) { 133 fprintf(stderr, 134 "Memory for prefixes exceeded in line %d\n", 135 linenum); 136 continue; 137 } 138 139 lineptr[strlen(lineptr) - 1] = 0; 140 for (i = 0; i < prefixcount; i++) { 141 if (!strcmp(prefixtable[i].prefixname, lineptr)) 142 break; 143 } 144 if (i < prefixcount) { 145 fprintf(stderr, "Redefinition of prefix '%s' " 146 "on line %d ignored\n", lineptr, linenum); 147 continue; /* skip duplicate prefix */ 148 } 149 150 prefixtable[prefixcount].prefixname = dupstr(lineptr); 151 lineptr += len + 1; 152 lineptr += strspn(lineptr, " \n\t"); 153 len = strcspn(lineptr, "\n\t"); 154 if (len == 0) { 155 fprintf(stderr, "Unexpected end of prefix on " 156 "line %d\n", linenum); 157 free(prefixtable[prefixcount].prefixname); 158 continue; 159 } 160 lineptr[len] = 0; 161 prefixtable[prefixcount++].prefixval = dupstr(lineptr); 162 } else { /* it's not a prefix */ 163 if (unitcount == MAXUNITS) { 164 fprintf(stderr, 165 "Memory for units exceeded in line %d\n", 166 linenum); 167 continue; 168 } 169 170 for (i = 0; i < unitcount; i++) { 171 if (!strcmp(unittable[i].uname, lineptr)) 172 break; 173 } 174 if (i < unitcount) { 175 fprintf(stderr, "Redefinition of unit '%s' " 176 "on line %d ignored\n", lineptr, linenum); 177 continue; /* skip duplicate unit */ 178 } 179 180 unittable[unitcount].uname = dupstr(lineptr); 181 lineptr += len + 1; 182 lineptr += strspn(lineptr, " \n\t"); 183 if (!strlen(lineptr)) { 184 fprintf(stderr, "Unexpected end of unit on " 185 "line %d\n", linenum); 186 free(unittable[unitcount].uname); 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 fprintf(stderr, "Memory overflow in unit reduction\n"); 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(void) 270 { 271 fprintf(stderr, "Unit reduces to zero\n"); 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 free(savescr); 312 return 1; 313 } 314 if (doingtop ^ flip) 315 theunit->factor *= num; 316 else 317 theunit->factor /= num; 318 num = atof(divider + 1); 319 if (!num) { 320 zeroerror(); 321 free(savescr); 322 return 1; 323 } 324 if (doingtop ^ flip) 325 theunit->factor /= num; 326 else 327 theunit->factor *= num; 328 } else { 329 num = atof(item); 330 if (!num) { 331 zeroerror(); 332 free(savescr); 333 return 1; 334 } 335 if (doingtop ^ flip) 336 theunit->factor *= num; 337 else 338 theunit->factor /= num; 339 340 } 341 } else { /* item is not a number */ 342 int repeat = 1; 343 344 if (strchr("23456789", 345 item[strlen(item) - 1])) { 346 repeat = item[strlen(item) - 1] - '0'; 347 item[strlen(item) - 1] = 0; 348 } 349 for (; repeat; repeat--) 350 if (addsubunit(doingtop ^ flip 351 ? theunit->numerator 352 : theunit->denominator, item)) { 353 free(savescr); 354 return 1; 355 } 356 } 357 item = strtok(NULL, " *\t/\n"); 358 } 359 doingtop--; 360 if (slash) { 361 scratch = slash + 1; 362 } else 363 doingtop--; 364 } while (doingtop >= 0); 365 free(savescr); 366 return 0; 367 } 368 369 370 int 371 compare(const void *item1, const void *item2) 372 { 373 return strcmp(*(char **) item1, *(char **) item2); 374 } 375 376 377 void 378 sortunit(struct unittype *theunit) 379 { 380 char **ptr; 381 int count; 382 383 for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++); 384 qsort(theunit->numerator, count, sizeof(char *), compare); 385 for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++); 386 qsort(theunit->denominator, count, sizeof(char *), compare); 387 } 388 389 390 void 391 cancelunit(struct unittype *theunit) 392 { 393 char **den, **num; 394 int comp; 395 396 den = theunit->denominator; 397 num = theunit->numerator; 398 399 while (*num && *den) { 400 comp = strcmp(*den, *num); 401 if (!comp) { 402 *den++ = NULLUNIT; 403 *num++ = NULLUNIT; 404 } else if (comp < 0) 405 den++; 406 else 407 num++; 408 } 409 } 410 411 412 413 414 /* 415 Looks up the definition for the specified unit. 416 Returns a pointer to the definition or a null pointer 417 if the specified unit does not appear in the units table. 418 */ 419 420 static char buffer[500]; /* buffer for lookupunit answers with 421 prefixes */ 422 423 char * 424 lookupunit(char *unit) 425 { 426 size_t len; 427 int i; 428 char *copy; 429 430 for (i = 0; i < unitcount; i++) { 431 if (!strcmp(unittable[i].uname, unit)) 432 return unittable[i].uval; 433 } 434 435 len = strlen(unit); 436 if (len == 0) 437 return NULL; 438 if (unit[len - 1] == '^') { 439 copy = dupstr(unit); 440 copy[len - 1] = '\0'; 441 for (i = 0; i < unitcount; i++) { 442 if (!strcmp(unittable[i].uname, copy)) { 443 strlcpy(buffer, copy, sizeof(buffer)); 444 free(copy); 445 return buffer; 446 } 447 } 448 free(copy); 449 } 450 if (unit[len - 1] == 's') { 451 copy = dupstr(unit); 452 copy[len - 1] = '\0'; 453 --len; 454 for (i = 0; i < unitcount; i++) { 455 if (!strcmp(unittable[i].uname, copy)) { 456 strlcpy(buffer, copy, sizeof(buffer)); 457 free(copy); 458 return buffer; 459 } 460 } 461 if (len != 0 && copy[len - 1] == 'e') { 462 copy[len - 1] = 0; 463 for (i = 0; i < unitcount; i++) { 464 if (!strcmp(unittable[i].uname, copy)) { 465 strlcpy(buffer, copy, sizeof(buffer)); 466 free(copy); 467 return buffer; 468 } 469 } 470 } 471 free(copy); 472 } 473 for (i = 0; i < prefixcount; i++) { 474 len = strlen(prefixtable[i].prefixname); 475 if (!strncmp(prefixtable[i].prefixname, unit, len)) { 476 if (!strlen(unit + len) || lookupunit(unit + len)) { 477 snprintf(buffer, sizeof(buffer), "%s %s", 478 prefixtable[i].prefixval, unit + len); 479 return buffer; 480 } 481 } 482 } 483 return NULL; 484 } 485 486 487 488 /* 489 reduces a product of symbolic units to primitive units. 490 The three low bits are used to return flags: 491 492 bit 0 (1) set on if reductions were performed without error. 493 bit 1 (2) set on if no reductions are performed. 494 bit 2 (4) set on if an unknown unit is discovered. 495 */ 496 497 498 #define ERROR 4 499 500 int 501 reduceproduct(struct unittype *theunit, int flip) 502 { 503 char *toadd, **product; 504 int didsomething = 2; 505 506 if (flip) 507 product = theunit->denominator; 508 else 509 product = theunit->numerator; 510 511 for (; *product; product++) { 512 513 for (;;) { 514 if (!strlen(*product)) 515 break; 516 toadd = lookupunit(*product); 517 if (!toadd) { 518 printf("unknown unit '%s'\n", *product); 519 return ERROR; 520 } 521 if (strchr(toadd, PRIMITIVECHAR)) 522 break; 523 didsomething = 1; 524 if (*product != NULLUNIT) { 525 free(*product); 526 *product = NULLUNIT; 527 } 528 if (addunit(theunit, toadd, flip)) 529 return ERROR; 530 } 531 } 532 return didsomething; 533 } 534 535 536 /* 537 Reduces numerator and denominator of the specified unit. 538 Returns 0 on success, or 1 on unknown unit error. 539 */ 540 541 int 542 reduceunit(struct unittype *theunit) 543 { 544 int ret; 545 546 ret = 1; 547 while (ret & 1) { 548 ret = reduceproduct(theunit, 0) | reduceproduct(theunit, 1); 549 if (ret & 4) 550 return 1; 551 } 552 return 0; 553 } 554 555 556 int 557 compareproducts(char **one, char **two) 558 { 559 while (*one || *two) { 560 if (!*one && *two != NULLUNIT) 561 return 1; 562 if (!*two && *one != NULLUNIT) 563 return 1; 564 if (*one == NULLUNIT) 565 one++; 566 else if (*two == NULLUNIT) 567 two++; 568 else if (strcmp(*one, *two)) 569 return 1; 570 else 571 one++, two++; 572 } 573 return 0; 574 } 575 576 577 /* Return zero if units are compatible, nonzero otherwise */ 578 579 int 580 compareunits(struct unittype *first, struct unittype *second) 581 { 582 return compareproducts(first->numerator, second->numerator) || 583 compareproducts(first->denominator, second->denominator); 584 } 585 586 587 int 588 completereduce(struct unittype *unit) 589 { 590 if (reduceunit(unit)) 591 return 1; 592 sortunit(unit); 593 cancelunit(unit); 594 return 0; 595 } 596 597 598 void 599 showanswer(struct unittype *have, struct unittype *want) 600 { 601 if (compareunits(have, want)) { 602 printf("conformability error\n"); 603 showunit(have); 604 showunit(want); 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(void) 613 { 614 fprintf(stderr, 615 "usage: units [-qv] [-f filename] [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 extern char *optarg; 631 extern int optind; 632 633 while ((optchar = getopt(argc, argv, "vqf:")) != -1) { 634 switch (optchar) { 635 case 'f': 636 userfile = optarg; 637 break; 638 case 'q': 639 quiet = 1; 640 break; 641 case 'v': 642 fprintf(stderr, 643 "units version %s Copyright (c) 1993 by Adrian Mariano\n", 644 VERSION); 645 fprintf(stderr, 646 "This program may be freely distributed\n"); 647 usage(); 648 default: 649 usage(); 650 break; 651 } 652 } 653 654 if (optind != argc - 2 && optind != argc) 655 usage(); 656 657 readunits(userfile); 658 659 if (optind == argc - 2) { 660 strlcpy(havestr, argv[optind], sizeof(havestr)); 661 strlcpy(wantstr, argv[optind + 1], sizeof(wantstr)); 662 initializeunit(&have); 663 addunit(&have, havestr, 0); 664 completereduce(&have); 665 initializeunit(&want); 666 addunit(&want, wantstr, 0); 667 completereduce(&want); 668 showanswer(&have, &want); 669 } else { 670 if (!quiet) 671 printf("%d units, %d prefixes\n", unitcount, 672 prefixcount); 673 for (;;) { 674 do { 675 initializeunit(&have); 676 if (!quiet) 677 printf("You have: "); 678 if (!fgets(havestr, sizeof(havestr), stdin)) { 679 if (!quiet) 680 putchar('\n'); 681 exit(0); 682 } 683 } while (addunit(&have, havestr, 0) || 684 completereduce(&have)); 685 do { 686 initializeunit(&want); 687 if (!quiet) 688 printf("You want: "); 689 if (!fgets(wantstr, sizeof(wantstr), stdin)) { 690 if (!quiet) 691 putchar('\n'); 692 exit(0); 693 } 694 } while (addunit(&want, wantstr, 0) || 695 completereduce(&want)); 696 showanswer(&have, &want); 697 } 698 } 699 return (0); 700 } 701