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