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