/usr.bin/units/units.c

https://bitbucket.org/freebsd/freebsd-head/ · C · 749 lines · 614 code · 91 blank · 44 comment · 155 complexity · bcf46b5d156ce35d9e2945df258c55d2 MD5 · raw file

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