1 1.27 dholland /* $NetBSD: units.c,v 1.27 2016/02/05 03:32:49 dholland Exp $ */ 2 1.6 thorpej 3 1.1 cgd /* 4 1.1 cgd * units.c Copyright (c) 1993 by Adrian Mariano (adrian (at) cam.cornell.edu) 5 1.1 cgd * 6 1.1 cgd * Redistribution and use in source and binary forms, with or without 7 1.1 cgd * modification, are permitted provided that the following conditions 8 1.1 cgd * are met: 9 1.1 cgd * 1. Redistributions of source code must retain the above copyright 10 1.1 cgd * notice, this list of conditions and the following disclaimer. 11 1.1 cgd * 2. The name of the author may not be used to endorse or promote products 12 1.2 jtc * derived from this software without specific prior written permission. 13 1.1 cgd * Disclaimer: This software is provided by the author "as is". The author 14 1.1 cgd * shall not be liable for any damages caused in any way by this software. 15 1.1 cgd * 16 1.1 cgd * I would appreciate (though I do not require) receiving a copy of any 17 1.1 cgd * improvements you might make to this program. 18 1.1 cgd */ 19 1.1 cgd 20 1.26 dholland #include <assert.h> 21 1.3 jtc #include <ctype.h> 22 1.7 lukem #include <err.h> 23 1.21 apb #include <float.h> 24 1.1 cgd #include <stdio.h> 25 1.1 cgd #include <string.h> 26 1.1 cgd #include <stdlib.h> 27 1.8 perry #include <unistd.h> 28 1.1 cgd 29 1.1 cgd #include "pathnames.h" 30 1.1 cgd 31 1.1 cgd #define VERSION "1.0" 32 1.1 cgd 33 1.1 cgd #ifndef UNITSFILE 34 1.1 cgd #define UNITSFILE _PATH_UNITSLIB 35 1.1 cgd #endif 36 1.1 cgd 37 1.1 cgd #define MAXUNITS 1000 38 1.1 cgd #define MAXPREFIXES 50 39 1.1 cgd 40 1.1 cgd #define MAXSUBUNITS 500 41 1.1 cgd 42 1.1 cgd #define PRIMITIVECHAR '!' 43 1.1 cgd 44 1.21 apb static int precision = 8; /* for printf with "%.*g" format */ 45 1.21 apb 46 1.21 apb static const char *errprefix = NULL; /* if not NULL, then prepend this 47 1.21 apb * to error messages and send them to 48 1.21 apb * stdout instead of stderr. 49 1.21 apb */ 50 1.21 apb 51 1.17 joerg static const char *powerstring = "^"; 52 1.1 cgd 53 1.17 joerg static struct { 54 1.16 lukem const char *uname; 55 1.16 lukem const char *uval; 56 1.1 cgd } unittable[MAXUNITS]; 57 1.1 cgd 58 1.1 cgd struct unittype { 59 1.16 lukem const char *numerator[MAXSUBUNITS]; 60 1.16 lukem const char *denominator[MAXSUBUNITS]; 61 1.1 cgd double factor; 62 1.1 cgd }; 63 1.1 cgd 64 1.1 cgd struct { 65 1.16 lukem const char *prefixname; 66 1.16 lukem const char *prefixval; 67 1.1 cgd } prefixtable[MAXPREFIXES]; 68 1.1 cgd 69 1.1 cgd 70 1.17 joerg static const char *NULLUNIT = ""; 71 1.1 cgd 72 1.17 joerg static int unitcount; 73 1.17 joerg static int prefixcount; 74 1.1 cgd 75 1.7 lukem 76 1.17 joerg static int addsubunit(const char *[], const char *); 77 1.17 joerg static int addunit(struct unittype *, const char *, int); 78 1.17 joerg static void cancelunit(struct unittype *); 79 1.17 joerg static int compare(const void *, const void *); 80 1.17 joerg static int compareproducts(const char **, const char **); 81 1.17 joerg static int compareunits(struct unittype *, struct unittype *); 82 1.17 joerg static int compareunitsreciprocal(struct unittype *, struct unittype *); 83 1.17 joerg static int completereduce(struct unittype *); 84 1.17 joerg static void initializeunit(struct unittype *); 85 1.17 joerg static void readerror(int); 86 1.17 joerg static void readunits(const char *); 87 1.17 joerg static int reduceproduct(struct unittype *, int); 88 1.17 joerg static int reduceunit(struct unittype *); 89 1.17 joerg static void showanswer(struct unittype *, struct unittype *); 90 1.17 joerg static void showunit(struct unittype *); 91 1.17 joerg static void sortunit(struct unittype *); 92 1.17 joerg __dead static void usage(void); 93 1.17 joerg static void zeroerror(void); 94 1.17 joerg static char *dupstr(const char *); 95 1.17 joerg static const char *lookupunit(const char *); 96 1.7 lukem 97 1.17 joerg static char * 98 1.16 lukem dupstr(const char *str) 99 1.1 cgd { 100 1.1 cgd char *ret; 101 1.1 cgd 102 1.12 itojun ret = strdup(str); 103 1.7 lukem if (!ret) 104 1.7 lukem err(3, "Memory allocation error"); 105 1.1 cgd return (ret); 106 1.1 cgd } 107 1.1 cgd 108 1.1 cgd 109 1.25 joerg static __printflike(1, 2) void 110 1.21 apb mywarnx(const char *fmt, ...) 111 1.21 apb { 112 1.21 apb va_list args; 113 1.21 apb 114 1.21 apb va_start(args, fmt); 115 1.21 apb if (errprefix) { 116 1.21 apb /* warn to stdout, with errprefix prepended */ 117 1.21 apb printf("%s", errprefix); 118 1.21 apb vprintf(fmt, args); 119 1.21 apb printf("%s", "\n"); 120 1.21 apb } else { 121 1.21 apb /* warn to stderr */ 122 1.21 apb vwarnx(fmt, args); 123 1.21 apb } 124 1.21 apb va_end(args); 125 1.21 apb } 126 1.21 apb 127 1.21 apb static void 128 1.1 cgd readerror(int linenum) 129 1.1 cgd { 130 1.21 apb mywarnx("Error in units file '%s' line %d", UNITSFILE, linenum); 131 1.1 cgd } 132 1.1 cgd 133 1.1 cgd 134 1.17 joerg static void 135 1.16 lukem readunits(const char *userfile) 136 1.1 cgd { 137 1.1 cgd FILE *unitfile; 138 1.1 cgd char line[80], *lineptr; 139 1.20 apb int len, linenum, i, isdup; 140 1.1 cgd 141 1.1 cgd unitcount = 0; 142 1.1 cgd linenum = 0; 143 1.1 cgd 144 1.1 cgd if (userfile) { 145 1.1 cgd unitfile = fopen(userfile, "rt"); 146 1.7 lukem if (!unitfile) 147 1.7 lukem err(1, "Unable to open units file '%s'", userfile); 148 1.1 cgd } 149 1.1 cgd else { 150 1.1 cgd unitfile = fopen(UNITSFILE, "rt"); 151 1.1 cgd if (!unitfile) { 152 1.1 cgd char *direc, *env; 153 1.1 cgd char filename[1000]; 154 1.1 cgd char separator[2]; 155 1.1 cgd 156 1.1 cgd env = getenv("PATH"); 157 1.1 cgd if (env) { 158 1.1 cgd if (strchr(env, ';')) 159 1.12 itojun strlcpy(separator, ";", 160 1.12 itojun sizeof(separator)); 161 1.1 cgd else 162 1.12 itojun strlcpy(separator, ":", 163 1.12 itojun sizeof(separator)); 164 1.1 cgd direc = strtok(env, separator); 165 1.1 cgd while (direc) { 166 1.12 itojun strlcpy(filename, "", sizeof(filename)); 167 1.12 itojun strlcat(filename, direc, 168 1.12 itojun sizeof(filename)); 169 1.12 itojun strlcat(filename, "/", 170 1.12 itojun sizeof(filename)); 171 1.12 itojun strlcat(filename, UNITSFILE, 172 1.12 itojun sizeof(filename)); 173 1.1 cgd unitfile = fopen(filename, "rt"); 174 1.1 cgd if (unitfile) 175 1.1 cgd break; 176 1.1 cgd direc = strtok(NULL, separator); 177 1.1 cgd } 178 1.1 cgd } 179 1.7 lukem if (!unitfile) 180 1.7 lukem errx(1, "Can't find units file '%s'", 181 1.1 cgd UNITSFILE); 182 1.1 cgd } 183 1.1 cgd } 184 1.1 cgd while (!feof(unitfile)) { 185 1.1 cgd if (!fgets(line, 79, unitfile)) 186 1.1 cgd break; 187 1.1 cgd linenum++; 188 1.1 cgd lineptr = line; 189 1.1 cgd if (*lineptr == '/') 190 1.1 cgd continue; 191 1.1 cgd lineptr += strspn(lineptr, " \n\t"); 192 1.1 cgd len = strcspn(lineptr, " \n\t"); 193 1.1 cgd lineptr[len] = 0; 194 1.1 cgd if (!strlen(lineptr)) 195 1.1 cgd continue; 196 1.1 cgd if (lineptr[strlen(lineptr) - 1] == '-') { /* it's a prefix */ 197 1.1 cgd if (prefixcount == MAXPREFIXES) { 198 1.21 apb mywarnx( 199 1.21 apb "Memory for prefixes exceeded in line %d", 200 1.21 apb linenum); 201 1.1 cgd continue; 202 1.1 cgd } 203 1.1 cgd lineptr[strlen(lineptr) - 1] = 0; 204 1.20 apb for (isdup = 0, i = 0; i < prefixcount; i++) { 205 1.20 apb if (!strcmp(prefixtable[i].prefixname, 206 1.20 apb lineptr)) { 207 1.20 apb isdup = 1; 208 1.20 apb break; 209 1.20 apb } 210 1.20 apb } 211 1.20 apb if (isdup) { 212 1.21 apb mywarnx( 213 1.20 apb "Redefinition of prefix '%s' on line %d ignored", 214 1.20 apb lineptr, linenum); 215 1.20 apb continue; 216 1.20 apb } 217 1.1 cgd prefixtable[prefixcount].prefixname = dupstr(lineptr); 218 1.1 cgd lineptr += len + 1; 219 1.1 cgd if (!strlen(lineptr)) { 220 1.1 cgd readerror(linenum); 221 1.1 cgd continue; 222 1.1 cgd } 223 1.1 cgd lineptr += strspn(lineptr, " \n\t"); 224 1.1 cgd len = strcspn(lineptr, "\n\t"); 225 1.1 cgd lineptr[len] = 0; 226 1.1 cgd prefixtable[prefixcount++].prefixval = dupstr(lineptr); 227 1.1 cgd } 228 1.1 cgd else { /* it's not a prefix */ 229 1.1 cgd if (unitcount == MAXUNITS) { 230 1.21 apb mywarnx("Memory for units exceeded in line %d", 231 1.1 cgd linenum); 232 1.1 cgd continue; 233 1.1 cgd } 234 1.20 apb for (isdup = 0, i = 0; i < unitcount; i++) { 235 1.1 cgd if (!strcmp(unittable[i].uname, lineptr)) { 236 1.20 apb isdup = 1; 237 1.20 apb break; 238 1.20 apb } 239 1.20 apb } 240 1.20 apb if (isdup) { 241 1.21 apb mywarnx( 242 1.7 lukem "Redefinition of unit '%s' on line %d ignored", 243 1.20 apb lineptr, linenum); 244 1.20 apb continue; 245 1.20 apb } 246 1.20 apb unittable[unitcount].uname = dupstr(lineptr); 247 1.1 cgd lineptr += len + 1; 248 1.1 cgd lineptr += strspn(lineptr, " \n\t"); 249 1.1 cgd if (!strlen(lineptr)) { 250 1.1 cgd readerror(linenum); 251 1.1 cgd continue; 252 1.1 cgd } 253 1.1 cgd len = strcspn(lineptr, "\n\t"); 254 1.1 cgd lineptr[len] = 0; 255 1.1 cgd unittable[unitcount++].uval = dupstr(lineptr); 256 1.1 cgd } 257 1.1 cgd } 258 1.1 cgd fclose(unitfile); 259 1.1 cgd } 260 1.1 cgd 261 1.17 joerg static void 262 1.1 cgd initializeunit(struct unittype * theunit) 263 1.1 cgd { 264 1.1 cgd theunit->factor = 1.0; 265 1.1 cgd theunit->numerator[0] = theunit->denominator[0] = NULL; 266 1.1 cgd } 267 1.1 cgd 268 1.17 joerg static int 269 1.16 lukem addsubunit(const char *product[], const char *toadd) 270 1.1 cgd { 271 1.16 lukem const char **ptr; 272 1.1 cgd 273 1.1 cgd for (ptr = product; *ptr && *ptr != NULLUNIT; ptr++); 274 1.1 cgd if (ptr >= product + MAXSUBUNITS) { 275 1.21 apb mywarnx("Memory overflow in unit reduction"); 276 1.1 cgd return 1; 277 1.1 cgd } 278 1.1 cgd if (!*ptr) 279 1.1 cgd *(ptr + 1) = 0; 280 1.1 cgd *ptr = dupstr(toadd); 281 1.1 cgd return 0; 282 1.1 cgd } 283 1.1 cgd 284 1.17 joerg static void 285 1.1 cgd showunit(struct unittype * theunit) 286 1.1 cgd { 287 1.16 lukem const char **ptr; 288 1.1 cgd int printedslash; 289 1.1 cgd int counter = 1; 290 1.1 cgd 291 1.21 apb printf("\t%.*g", precision, theunit->factor); 292 1.1 cgd for (ptr = theunit->numerator; *ptr; ptr++) { 293 1.1 cgd if (ptr > theunit->numerator && **ptr && 294 1.1 cgd !strcmp(*ptr, *(ptr - 1))) 295 1.1 cgd counter++; 296 1.1 cgd else { 297 1.1 cgd if (counter > 1) 298 1.1 cgd printf("%s%d", powerstring, counter); 299 1.1 cgd if (**ptr) 300 1.1 cgd printf(" %s", *ptr); 301 1.1 cgd counter = 1; 302 1.1 cgd } 303 1.1 cgd } 304 1.1 cgd if (counter > 1) 305 1.1 cgd printf("%s%d", powerstring, counter); 306 1.1 cgd counter = 1; 307 1.1 cgd printedslash = 0; 308 1.1 cgd for (ptr = theunit->denominator; *ptr; ptr++) { 309 1.1 cgd if (ptr > theunit->denominator && **ptr && 310 1.1 cgd !strcmp(*ptr, *(ptr - 1))) 311 1.1 cgd counter++; 312 1.1 cgd else { 313 1.1 cgd if (counter > 1) 314 1.1 cgd printf("%s%d", powerstring, counter); 315 1.1 cgd if (**ptr) { 316 1.1 cgd if (!printedslash) 317 1.1 cgd printf(" /"); 318 1.1 cgd printedslash = 1; 319 1.1 cgd printf(" %s", *ptr); 320 1.1 cgd } 321 1.1 cgd counter = 1; 322 1.1 cgd } 323 1.1 cgd } 324 1.1 cgd if (counter > 1) 325 1.1 cgd printf("%s%d", powerstring, counter); 326 1.1 cgd printf("\n"); 327 1.1 cgd } 328 1.1 cgd 329 1.17 joerg static void 330 1.18 matt zeroerror(void) 331 1.1 cgd { 332 1.21 apb mywarnx("Unit reduces to zero"); 333 1.1 cgd } 334 1.1 cgd 335 1.1 cgd /* 336 1.1 cgd Adds the specified string to the unit. 337 1.1 cgd Flip is 0 for adding normally, 1 for adding reciprocal. 338 1.1 cgd 339 1.1 cgd Returns 0 for successful addition, nonzero on error. 340 1.1 cgd */ 341 1.1 cgd 342 1.17 joerg static int 343 1.16 lukem addunit(struct unittype * theunit, const char *toadd, int flip) 344 1.1 cgd { 345 1.1 cgd char *scratch, *savescr; 346 1.1 cgd char *item; 347 1.3 jtc char *divider, *slash; 348 1.26 dholland char *minus; 349 1.26 dholland size_t pos, len; 350 1.1 cgd int doingtop; 351 1.1 cgd 352 1.1 cgd savescr = scratch = dupstr(toadd); 353 1.26 dholland 354 1.26 dholland /* 355 1.26 dholland * "foot-pound" is the same as "foot pound". But don't 356 1.26 dholland * trash minus signs on numbers. 357 1.26 dholland * 358 1.26 dholland * 20160204 dholland: this used to let through only minus 359 1.26 dholland * signs at the beginning of the string or in the middle of a 360 1.26 dholland * floating constant (e.g. 3.6e-5), and a minus sign at the 361 1.26 dholland * beginning of the string failed further on. I have changed 362 1.26 dholland * it so any minus sign before a digit (or decimal point) is 363 1.26 dholland * treated as going with that digit. 364 1.26 dholland * 365 1.26 dholland * Note that this changed the interpretation of certain 366 1.26 dholland * marginally valid inputs like "3 N-5 s"; that used to be 367 1.26 dholland * interpreted as "3 N 5 s" or 15 N s, but now it reads as 368 1.26 dholland * "3 N -5 s" or -15 N s. However, it also makes negative 369 1.26 dholland * exponents on units work, which used to be silently trashed. 370 1.26 dholland */ 371 1.26 dholland for (minus = scratch + 1; *minus; minus++) { 372 1.26 dholland if (*minus != '-') { 373 1.26 dholland continue; 374 1.26 dholland } 375 1.26 dholland if (strchr(".0123456789", *(minus + 1))) { 376 1.26 dholland continue; 377 1.26 dholland } 378 1.26 dholland *minus = ' '; 379 1.26 dholland } 380 1.26 dholland 381 1.26 dholland /* Process up to the next / in one go. */ 382 1.26 dholland 383 1.1 cgd slash = strchr(scratch, '/'); 384 1.1 cgd if (slash) 385 1.1 cgd *slash = 0; 386 1.1 cgd doingtop = 1; 387 1.1 cgd do { 388 1.1 cgd item = strtok(scratch, " *\t\n/"); 389 1.1 cgd while (item) { 390 1.26 dholland if ((*item == '-' && strchr("0123456789.", *(item+1))) 391 1.26 dholland || strchr("0123456789.", *item)) { 392 1.26 dholland 393 1.19 apb /* item starts with a number */ 394 1.19 apb char *endptr; 395 1.1 cgd double num; 396 1.1 cgd 397 1.1 cgd divider = strchr(item, '|'); 398 1.1 cgd if (divider) { 399 1.1 cgd *divider = 0; 400 1.19 apb num = strtod(item, &endptr); 401 1.1 cgd if (!num) { 402 1.1 cgd zeroerror(); 403 1.1 cgd return 1; 404 1.1 cgd } 405 1.19 apb if (endptr != divider) { 406 1.19 apb /* "6foo|2" is an error */ 407 1.21 apb mywarnx("Junk before '|'"); 408 1.19 apb return 1; 409 1.19 apb } 410 1.1 cgd if (doingtop ^ flip) 411 1.1 cgd theunit->factor *= num; 412 1.1 cgd else 413 1.1 cgd theunit->factor /= num; 414 1.19 apb num = strtod(divider + 1, &endptr); 415 1.1 cgd if (!num) { 416 1.1 cgd zeroerror(); 417 1.1 cgd return 1; 418 1.1 cgd } 419 1.1 cgd if (doingtop ^ flip) 420 1.1 cgd theunit->factor /= num; 421 1.1 cgd else 422 1.1 cgd theunit->factor *= num; 423 1.19 apb if (*endptr) { 424 1.19 apb /* "6|2foo" is like "6|2 foo" */ 425 1.19 apb item = endptr; 426 1.19 apb continue; 427 1.19 apb } 428 1.1 cgd } 429 1.1 cgd else { 430 1.19 apb num = strtod(item, &endptr); 431 1.1 cgd if (!num) { 432 1.1 cgd zeroerror(); 433 1.1 cgd return 1; 434 1.1 cgd } 435 1.1 cgd if (doingtop ^ flip) 436 1.1 cgd theunit->factor *= num; 437 1.1 cgd else 438 1.1 cgd theunit->factor /= num; 439 1.19 apb if (*endptr) { 440 1.19 apb /* "3foo" is like "3 foo" */ 441 1.19 apb item = endptr; 442 1.19 apb continue; 443 1.19 apb } 444 1.1 cgd } 445 1.1 cgd } 446 1.1 cgd else { /* item is not a number */ 447 1.1 cgd int repeat = 1; 448 1.26 dholland int flipthis = 0; 449 1.1 cgd 450 1.26 dholland pos = len = strlen(item); 451 1.26 dholland assert(pos > 0); 452 1.26 dholland while (strchr("0123456789", item[pos - 1])) { 453 1.26 dholland pos--; 454 1.26 dholland /* string began with non-digit */ 455 1.26 dholland assert(pos > 0); 456 1.26 dholland } 457 1.26 dholland if (pos < len) { 458 1.27 dholland if (pos > 1 && item[pos - 1] == '-' && 459 1.27 dholland item[pos - 2] == '^') { 460 1.26 dholland /* allow negative exponents */ 461 1.26 dholland pos--; 462 1.26 dholland } 463 1.26 dholland /* have an exponent */ 464 1.26 dholland repeat = strtol(item + pos, NULL, 10); 465 1.26 dholland item[pos] = 0; 466 1.26 dholland if (repeat == 0) { 467 1.26 dholland /* not really the right msg */ 468 1.26 dholland zeroerror(); 469 1.26 dholland return 1; 470 1.26 dholland } 471 1.26 dholland if (repeat < 0) { 472 1.26 dholland flipthis = 1; 473 1.26 dholland repeat = -repeat; 474 1.26 dholland } 475 1.1 cgd } 476 1.26 dholland flipthis ^= doingtop ^ flip; 477 1.1 cgd for (; repeat; repeat--) 478 1.26 dholland if (addsubunit(flipthis ? theunit->numerator : theunit->denominator, item)) 479 1.1 cgd return 1; 480 1.1 cgd } 481 1.1 cgd item = strtok(NULL, " *\t/\n"); 482 1.1 cgd } 483 1.1 cgd doingtop--; 484 1.1 cgd if (slash) { 485 1.1 cgd scratch = slash + 1; 486 1.1 cgd } 487 1.1 cgd else 488 1.1 cgd doingtop--; 489 1.1 cgd } while (doingtop >= 0); 490 1.1 cgd free(savescr); 491 1.1 cgd return 0; 492 1.1 cgd } 493 1.1 cgd 494 1.17 joerg static int 495 1.1 cgd compare(const void *item1, const void *item2) 496 1.1 cgd { 497 1.16 lukem return strcmp(*(const char * const *) item1, 498 1.16 lukem *(const char * const *) item2); 499 1.1 cgd } 500 1.1 cgd 501 1.17 joerg static void 502 1.1 cgd sortunit(struct unittype * theunit) 503 1.1 cgd { 504 1.16 lukem const char **ptr; 505 1.1 cgd int count; 506 1.1 cgd 507 1.1 cgd for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++); 508 1.1 cgd qsort(theunit->numerator, count, sizeof(char *), compare); 509 1.1 cgd for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++); 510 1.1 cgd qsort(theunit->denominator, count, sizeof(char *), compare); 511 1.1 cgd } 512 1.1 cgd 513 1.17 joerg static void 514 1.1 cgd cancelunit(struct unittype * theunit) 515 1.1 cgd { 516 1.16 lukem const char **den, **num; 517 1.1 cgd int comp; 518 1.1 cgd 519 1.1 cgd den = theunit->denominator; 520 1.1 cgd num = theunit->numerator; 521 1.1 cgd 522 1.1 cgd while (*num && *den) { 523 1.1 cgd comp = strcmp(*den, *num); 524 1.1 cgd if (!comp) { 525 1.1 cgd /* if (*den!=NULLUNIT) free(*den); 526 1.1 cgd if (*num!=NULLUNIT) free(*num);*/ 527 1.1 cgd *den++ = NULLUNIT; 528 1.1 cgd *num++ = NULLUNIT; 529 1.1 cgd } 530 1.1 cgd else if (comp < 0) 531 1.1 cgd den++; 532 1.1 cgd else 533 1.1 cgd num++; 534 1.1 cgd } 535 1.1 cgd } 536 1.1 cgd 537 1.1 cgd 538 1.1 cgd 539 1.1 cgd 540 1.1 cgd /* 541 1.1 cgd Looks up the definition for the specified unit. 542 1.1 cgd Returns a pointer to the definition or a null pointer 543 1.1 cgd if the specified unit does not appear in the units table. 544 1.1 cgd */ 545 1.1 cgd 546 1.1 cgd static char buffer[100]; /* buffer for lookupunit answers with 547 1.1 cgd prefixes */ 548 1.1 cgd 549 1.17 joerg static const char * 550 1.16 lukem lookupunit(const char *unit) 551 1.1 cgd { 552 1.1 cgd int i; 553 1.1 cgd char *copy; 554 1.1 cgd 555 1.1 cgd for (i = 0; i < unitcount; i++) { 556 1.1 cgd if (!strcmp(unittable[i].uname, unit)) 557 1.1 cgd return unittable[i].uval; 558 1.1 cgd } 559 1.1 cgd 560 1.1 cgd if (unit[strlen(unit) - 1] == '^') { 561 1.1 cgd copy = dupstr(unit); 562 1.1 cgd copy[strlen(copy) - 1] = 0; 563 1.1 cgd for (i = 0; i < unitcount; i++) { 564 1.1 cgd if (!strcmp(unittable[i].uname, copy)) { 565 1.12 itojun strlcpy(buffer, copy, sizeof(buffer)); 566 1.1 cgd free(copy); 567 1.1 cgd return buffer; 568 1.1 cgd } 569 1.1 cgd } 570 1.1 cgd free(copy); 571 1.1 cgd } 572 1.1 cgd if (unit[strlen(unit) - 1] == 's') { 573 1.1 cgd copy = dupstr(unit); 574 1.1 cgd copy[strlen(copy) - 1] = 0; 575 1.1 cgd for (i = 0; i < unitcount; i++) { 576 1.1 cgd if (!strcmp(unittable[i].uname, copy)) { 577 1.12 itojun strlcpy(buffer, copy, sizeof(buffer)); 578 1.1 cgd free(copy); 579 1.1 cgd return buffer; 580 1.1 cgd } 581 1.1 cgd } 582 1.1 cgd if (copy[strlen(copy) - 1] == 'e') { 583 1.1 cgd copy[strlen(copy) - 1] = 0; 584 1.1 cgd for (i = 0; i < unitcount; i++) { 585 1.1 cgd if (!strcmp(unittable[i].uname, copy)) { 586 1.12 itojun strlcpy(buffer, copy, sizeof(buffer)); 587 1.1 cgd free(copy); 588 1.1 cgd return buffer; 589 1.1 cgd } 590 1.1 cgd } 591 1.1 cgd } 592 1.1 cgd free(copy); 593 1.1 cgd } 594 1.1 cgd for (i = 0; i < prefixcount; i++) { 595 1.1 cgd if (!strncmp(prefixtable[i].prefixname, unit, 596 1.1 cgd strlen(prefixtable[i].prefixname))) { 597 1.1 cgd unit += strlen(prefixtable[i].prefixname); 598 1.1 cgd if (!strlen(unit) || lookupunit(unit)) { 599 1.12 itojun strlcpy(buffer, prefixtable[i].prefixval, 600 1.12 itojun sizeof(buffer)); 601 1.12 itojun strlcat(buffer, " ", sizeof(buffer)); 602 1.12 itojun strlcat(buffer, unit, sizeof(buffer)); 603 1.1 cgd return buffer; 604 1.1 cgd } 605 1.1 cgd } 606 1.1 cgd } 607 1.1 cgd return 0; 608 1.1 cgd } 609 1.1 cgd 610 1.1 cgd 611 1.1 cgd 612 1.1 cgd /* 613 1.1 cgd reduces a product of symbolic units to primitive units. 614 1.1 cgd The three low bits are used to return flags: 615 1.1 cgd 616 1.1 cgd bit 0 (1) set on if reductions were performed without error. 617 1.1 cgd bit 1 (2) set on if no reductions are performed. 618 1.1 cgd bit 2 (4) set on if an unknown unit is discovered. 619 1.1 cgd */ 620 1.1 cgd 621 1.1 cgd 622 1.1 cgd #define ERROR 4 623 1.1 cgd 624 1.17 joerg static int 625 1.1 cgd reduceproduct(struct unittype * theunit, int flip) 626 1.1 cgd { 627 1.1 cgd 628 1.16 lukem const char *toadd; 629 1.16 lukem const char **product; 630 1.1 cgd int didsomething = 2; 631 1.1 cgd 632 1.1 cgd if (flip) 633 1.1 cgd product = theunit->denominator; 634 1.1 cgd else 635 1.1 cgd product = theunit->numerator; 636 1.1 cgd 637 1.1 cgd for (; *product; product++) { 638 1.1 cgd 639 1.1 cgd for (;;) { 640 1.1 cgd if (!strlen(*product)) 641 1.1 cgd break; 642 1.1 cgd toadd = lookupunit(*product); 643 1.1 cgd if (!toadd) { 644 1.21 apb mywarnx("Unknown unit '%s'", *product); 645 1.1 cgd return ERROR; 646 1.1 cgd } 647 1.1 cgd if (strchr(toadd, PRIMITIVECHAR)) 648 1.1 cgd break; 649 1.1 cgd didsomething = 1; 650 1.1 cgd if (*product != NULLUNIT) { 651 1.16 lukem free(__UNCONST(*product)); 652 1.1 cgd *product = NULLUNIT; 653 1.1 cgd } 654 1.1 cgd if (addunit(theunit, toadd, flip)) 655 1.1 cgd return ERROR; 656 1.1 cgd } 657 1.1 cgd } 658 1.1 cgd return didsomething; 659 1.1 cgd } 660 1.1 cgd 661 1.1 cgd 662 1.1 cgd /* 663 1.1 cgd Reduces numerator and denominator of the specified unit. 664 1.1 cgd Returns 0 on success, or 1 on unknown unit error. 665 1.1 cgd */ 666 1.1 cgd 667 1.17 joerg static int 668 1.1 cgd reduceunit(struct unittype * theunit) 669 1.1 cgd { 670 1.1 cgd int ret; 671 1.1 cgd 672 1.1 cgd ret = 1; 673 1.1 cgd while (ret & 1) { 674 1.1 cgd ret = reduceproduct(theunit, 0) | reduceproduct(theunit, 1); 675 1.1 cgd if (ret & 4) 676 1.1 cgd return 1; 677 1.1 cgd } 678 1.1 cgd return 0; 679 1.1 cgd } 680 1.1 cgd 681 1.17 joerg static int 682 1.16 lukem compareproducts(const char **one, const char **two) 683 1.1 cgd { 684 1.1 cgd while (*one || *two) { 685 1.1 cgd if (!*one && *two != NULLUNIT) 686 1.1 cgd return 1; 687 1.1 cgd if (!*two && *one != NULLUNIT) 688 1.1 cgd return 1; 689 1.1 cgd if (*one == NULLUNIT) 690 1.1 cgd one++; 691 1.1 cgd else if (*two == NULLUNIT) 692 1.1 cgd two++; 693 1.15 christos else if (*one && *two && strcmp(*one, *two)) 694 1.1 cgd return 1; 695 1.1 cgd else 696 1.1 cgd one++, two++; 697 1.1 cgd } 698 1.1 cgd return 0; 699 1.1 cgd } 700 1.1 cgd 701 1.1 cgd 702 1.1 cgd /* Return zero if units are compatible, nonzero otherwise */ 703 1.1 cgd 704 1.17 joerg static int 705 1.1 cgd compareunits(struct unittype * first, struct unittype * second) 706 1.1 cgd { 707 1.1 cgd return 708 1.1 cgd compareproducts(first->numerator, second->numerator) || 709 1.1 cgd compareproducts(first->denominator, second->denominator); 710 1.1 cgd } 711 1.1 cgd 712 1.17 joerg static int 713 1.13 mycroft compareunitsreciprocal(struct unittype * first, struct unittype * second) 714 1.13 mycroft { 715 1.13 mycroft return 716 1.13 mycroft compareproducts(first->numerator, second->denominator) || 717 1.13 mycroft compareproducts(first->denominator, second->numerator); 718 1.13 mycroft } 719 1.13 mycroft 720 1.1 cgd 721 1.17 joerg static int 722 1.1 cgd completereduce(struct unittype * unit) 723 1.1 cgd { 724 1.1 cgd if (reduceunit(unit)) 725 1.1 cgd return 1; 726 1.1 cgd sortunit(unit); 727 1.1 cgd cancelunit(unit); 728 1.1 cgd return 0; 729 1.1 cgd } 730 1.1 cgd 731 1.1 cgd 732 1.17 joerg static void 733 1.1 cgd showanswer(struct unittype * have, struct unittype * want) 734 1.1 cgd { 735 1.1 cgd if (compareunits(have, want)) { 736 1.13 mycroft if (compareunitsreciprocal(have, want)) { 737 1.13 mycroft printf("conformability error\n"); 738 1.13 mycroft showunit(have); 739 1.13 mycroft showunit(want); 740 1.13 mycroft } else { 741 1.13 mycroft printf("\treciprocal conversion\n"); 742 1.21 apb printf("\t* %.*g\n\t/ %.*g\n", 743 1.21 apb precision, 1 / (have->factor * want->factor), 744 1.21 apb precision, want->factor * have->factor); 745 1.13 mycroft } 746 1.1 cgd } 747 1.1 cgd else 748 1.21 apb printf("\t* %.*g\n\t/ %.*g\n", 749 1.21 apb precision, have->factor / want->factor, 750 1.21 apb precision, want->factor / have->factor); 751 1.1 cgd } 752 1.1 cgd 753 1.21 apb static int 754 1.21 apb listunits(int expand) 755 1.21 apb { 756 1.21 apb struct unittype theunit; 757 1.21 apb const char *thename; 758 1.21 apb const char *thedefn; 759 1.21 apb int errors = 0; 760 1.21 apb int i; 761 1.21 apb int printexpansion; 762 1.21 apb 763 1.21 apb /* 764 1.21 apb * send error and warning messages to stdout, 765 1.21 apb * and make them look like comments. 766 1.21 apb */ 767 1.21 apb errprefix = "/ "; 768 1.21 apb 769 1.21 apb #if 0 /* debug */ 770 1.21 apb printf("/ expand=%d precision=%d unitcount=%d prefixcount=%d\n", 771 1.21 apb expand, precision, unitcount, prefixcount); 772 1.21 apb #endif 773 1.21 apb 774 1.21 apb /* 1. Dump all primitive units, e.g. "m !a!", "kg !b!", ... */ 775 1.21 apb printf("/ Primitive units\n"); 776 1.21 apb for (i = 0; i < unitcount; i++) { 777 1.21 apb thename = unittable[i].uname; 778 1.21 apb thedefn = unittable[i].uval; 779 1.21 apb if (thedefn[0] == PRIMITIVECHAR) { 780 1.21 apb printf("%s\t%s\n", thename, thedefn); 781 1.21 apb } 782 1.21 apb } 783 1.21 apb 784 1.21 apb /* 2. Dump all prefixes, e.g. "yotta- 1e24", "zetta- 1e21", ... */ 785 1.21 apb printf("/ Prefixes\n"); 786 1.21 apb for (i = 0; i < prefixcount; i++) { 787 1.21 apb printexpansion = expand; 788 1.21 apb thename = prefixtable[i].prefixname; 789 1.21 apb thedefn = prefixtable[i].prefixval; 790 1.21 apb if (expand) { 791 1.21 apb /* 792 1.21 apb * prefix names are sometimes identical to unit 793 1.21 apb * names, so we have to expand thedefn instead of 794 1.21 apb * expanding thename. 795 1.21 apb */ 796 1.21 apb initializeunit(&theunit); 797 1.21 apb if (addunit(&theunit, thedefn, 0) != 0 798 1.21 apb || completereduce(&theunit) != 0) { 799 1.21 apb errors++; 800 1.21 apb printexpansion = 0; 801 1.21 apb mywarnx("Error in prefix '%s-'", thename); 802 1.21 apb } 803 1.21 apb } 804 1.21 apb if (printexpansion) { 805 1.21 apb printf("%s-", thename); 806 1.21 apb showunit(&theunit); 807 1.21 apb } else 808 1.21 apb printf("%s-\t%s\n", thename, thedefn); 809 1.21 apb } 810 1.21 apb 811 1.21 apb /* 3. Dump all other units. */ 812 1.21 apb printf("/ Other units\n"); 813 1.21 apb for (i = 0; i < unitcount; i++) { 814 1.21 apb printexpansion = expand; 815 1.21 apb thename = unittable[i].uname; 816 1.21 apb thedefn = unittable[i].uval; 817 1.21 apb if (thedefn[0] == PRIMITIVECHAR) 818 1.21 apb continue; 819 1.21 apb if (expand) { 820 1.21 apb /* 821 1.21 apb * expand thename, not thedefn, so that 822 1.21 apb * we can catch errors in the name itself. 823 1.21 apb * e.g. a name that contains a hyphen 824 1.22 apb * will be interpreted as multiplication. 825 1.21 apb */ 826 1.21 apb initializeunit(&theunit); 827 1.23 apb if (addunit(&theunit, thename, 0) != 0 828 1.21 apb || completereduce(&theunit) != 0) { 829 1.21 apb errors++; 830 1.21 apb printexpansion = 0; 831 1.21 apb mywarnx("Error in unit '%s'", thename); 832 1.21 apb } 833 1.21 apb } 834 1.21 apb if (printexpansion) { 835 1.21 apb printf("%s", thename); 836 1.21 apb showunit(&theunit); 837 1.21 apb } else 838 1.21 apb printf("%s\t%s\n", thename, thedefn); 839 1.21 apb } 840 1.21 apb 841 1.21 apb if (errors) 842 1.21 apb mywarnx("Definitions with errors: %d", errors); 843 1.21 apb return (errors ? 1 : 0); 844 1.21 apb } 845 1.1 cgd 846 1.17 joerg static void 847 1.17 joerg usage(void) 848 1.1 cgd { 849 1.7 lukem fprintf(stderr, 850 1.24 wiz "\nunits [-Llqv] [-f filename] [[count] from-unit to-unit]\n"); 851 1.1 cgd fprintf(stderr, "\n -f specify units file\n"); 852 1.24 wiz fprintf(stderr, " -L list units in standardized base units\n"); 853 1.24 wiz fprintf(stderr, " -l list units\n"); 854 1.10 atatat fprintf(stderr, " -q suppress prompting (quiet)\n"); 855 1.1 cgd fprintf(stderr, " -v print version number\n"); 856 1.1 cgd exit(3); 857 1.1 cgd } 858 1.1 cgd 859 1.4 jtc int 860 1.1 cgd main(int argc, char **argv) 861 1.1 cgd { 862 1.1 cgd 863 1.1 cgd struct unittype have, want; 864 1.1 cgd char havestr[81], wantstr[81]; 865 1.5 mark int optchar; 866 1.16 lukem const char *userfile = 0; 867 1.21 apb int list = 0, listexpand = 0; 868 1.1 cgd int quiet = 0; 869 1.1 cgd 870 1.21 apb while ((optchar = getopt(argc, argv, "lLvqf:")) != -1) { 871 1.1 cgd switch (optchar) { 872 1.21 apb case 'l': 873 1.21 apb list = 1; 874 1.21 apb break; 875 1.21 apb case 'L': 876 1.21 apb list = 1; 877 1.21 apb listexpand = 1; 878 1.21 apb precision = DBL_DIG; 879 1.21 apb break; 880 1.1 cgd case 'f': 881 1.1 cgd userfile = optarg; 882 1.1 cgd break; 883 1.1 cgd case 'q': 884 1.1 cgd quiet = 1; 885 1.1 cgd break; 886 1.1 cgd case 'v': 887 1.1 cgd fprintf(stderr, "\n units version %s Copyright (c) 1993 by Adrian Mariano\n", 888 1.1 cgd VERSION); 889 1.1 cgd fprintf(stderr, " This program may be freely distributed\n"); 890 1.1 cgd usage(); 891 1.1 cgd default: 892 1.1 cgd usage(); 893 1.1 cgd break; 894 1.1 cgd } 895 1.1 cgd } 896 1.1 cgd 897 1.10 atatat argc -= optind; 898 1.10 atatat argv += optind; 899 1.10 atatat 900 1.21 apb if ((argc != 3 && argc != 2 && argc != 0) 901 1.21 apb || (list && argc != 0)) 902 1.1 cgd usage(); 903 1.1 cgd 904 1.21 apb if (list) 905 1.21 apb errprefix = "/ "; /* set this before reading the file */ 906 1.21 apb 907 1.1 cgd readunits(userfile); 908 1.1 cgd 909 1.21 apb if (list) 910 1.21 apb return listunits(listexpand); 911 1.21 apb 912 1.10 atatat if (argc == 3) { 913 1.12 itojun strlcpy(havestr, argv[0], sizeof(havestr)); 914 1.12 itojun strlcat(havestr, " ", sizeof(havestr)); 915 1.12 itojun strlcat(havestr, argv[1], sizeof(havestr)); 916 1.10 atatat argc--; 917 1.10 atatat argv++; 918 1.10 atatat argv[0] = havestr; 919 1.10 atatat } 920 1.10 atatat 921 1.10 atatat if (argc == 2) { 922 1.12 itojun strlcpy(havestr, argv[0], sizeof(havestr)); 923 1.12 itojun strlcpy(wantstr, argv[1], sizeof(wantstr)); 924 1.1 cgd initializeunit(&have); 925 1.1 cgd addunit(&have, havestr, 0); 926 1.1 cgd completereduce(&have); 927 1.1 cgd initializeunit(&want); 928 1.1 cgd addunit(&want, wantstr, 0); 929 1.1 cgd completereduce(&want); 930 1.1 cgd showanswer(&have, &want); 931 1.1 cgd } 932 1.1 cgd else { 933 1.1 cgd if (!quiet) 934 1.1 cgd printf("%d units, %d prefixes\n\n", unitcount, 935 1.1 cgd prefixcount); 936 1.1 cgd for (;;) { 937 1.1 cgd do { 938 1.1 cgd initializeunit(&have); 939 1.1 cgd if (!quiet) 940 1.1 cgd printf("You have: "); 941 1.1 cgd if (!fgets(havestr, 80, stdin)) { 942 1.11 kristerw if (!quiet) 943 1.11 kristerw putchar('\n'); 944 1.1 cgd exit(0); 945 1.1 cgd } 946 1.1 cgd } while (addunit(&have, havestr, 0) || 947 1.1 cgd completereduce(&have)); 948 1.1 cgd do { 949 1.1 cgd initializeunit(&want); 950 1.1 cgd if (!quiet) 951 1.1 cgd printf("You want: "); 952 1.1 cgd if (!fgets(wantstr, 80, stdin)) { 953 1.1 cgd if (!quiet) 954 1.1 cgd putchar('\n'); 955 1.1 cgd exit(0); 956 1.1 cgd } 957 1.1 cgd } while (addunit(&want, wantstr, 0) || 958 1.1 cgd completereduce(&want)); 959 1.1 cgd showanswer(&have, &want); 960 1.1 cgd } 961 1.1 cgd } 962 1.7 lukem return (0); 963 1.1 cgd } 964