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