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