11de7b4b8SPedro F. Giffuni /*- 21de7b4b8SPedro F. Giffuni * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 31de7b4b8SPedro F. Giffuni * 4a3031715SAlexander Langer * units.c Copyright (c) 1993 by Adrian Mariano (adrian@cam.cornell.edu) 5a3031715SAlexander Langer * 6a3031715SAlexander Langer * Redistribution and use in source and binary forms, with or without 7a3031715SAlexander Langer * modification, are permitted provided that the following conditions 8a3031715SAlexander Langer * are met: 9a3031715SAlexander Langer * 1. Redistributions of source code must retain the above copyright 10a3031715SAlexander Langer * notice, this list of conditions and the following disclaimer. 11a3031715SAlexander Langer * 2. The name of the author may not be used to endorse or promote products 12a3031715SAlexander Langer * derived from this software without specific prior written permission. 13a3031715SAlexander Langer * Disclaimer: This software is provided by the author "as is". The author 14a3031715SAlexander Langer * shall not be liable for any damages caused in any way by this software. 15a3031715SAlexander Langer * 16a3031715SAlexander Langer * I would appreciate (though I do not require) receiving a copy of any 17a3031715SAlexander Langer * improvements you might make to this program. 18a3031715SAlexander Langer */ 19a3031715SAlexander Langer 20d4d0763aSPhilippe Charnier #ifndef lint 21d4d0763aSPhilippe Charnier static const char rcsid[] = 22c3aac50fSPeter Wemm "$FreeBSD$"; 23d4d0763aSPhilippe Charnier #endif /* not lint */ 24d4d0763aSPhilippe Charnier 25a3031715SAlexander Langer #include <ctype.h> 26d4d0763aSPhilippe Charnier #include <err.h> 27ac117d5dSEitan Adler #include <errno.h> 28647be2c3SEitan Adler #include <histedit.h> 295192ff45SEitan Adler #include <getopt.h> 30647be2c3SEitan Adler #include <stdbool.h> 31a3031715SAlexander Langer #include <stdio.h> 32a3031715SAlexander Langer #include <stdlib.h> 33d4d0763aSPhilippe Charnier #include <string.h> 34d4d0763aSPhilippe Charnier #include <unistd.h> 35a3031715SAlexander Langer 367672a014SMariusz Zaborski #include <capsicum_helpers.h> 37ac117d5dSEitan Adler 38a3031715SAlexander Langer #ifndef UNITSFILE 39c706c470SEitan Adler #define UNITSFILE "/usr/share/misc/definitions.units" 40a3031715SAlexander Langer #endif 41a3031715SAlexander Langer 42a3031715SAlexander Langer #define MAXUNITS 1000 4317ad860fSDavid Malone #define MAXPREFIXES 100 44a3031715SAlexander Langer 45a3031715SAlexander Langer #define MAXSUBUNITS 500 46a3031715SAlexander Langer 47a3031715SAlexander Langer #define PRIMITIVECHAR '!' 48a3031715SAlexander Langer 4990a29505SEd Schouten static const char *powerstring = "^"; 50c706c470SEitan Adler static const char *numfmt = "%.8g"; 51a3031715SAlexander Langer 5290a29505SEd Schouten static struct { 53a3031715SAlexander Langer char *uname; 54a3031715SAlexander Langer char *uval; 55a3031715SAlexander Langer } unittable[MAXUNITS]; 56a3031715SAlexander Langer 57a3031715SAlexander Langer struct unittype { 58a3031715SAlexander Langer char *numerator[MAXSUBUNITS]; 59a3031715SAlexander Langer char *denominator[MAXSUBUNITS]; 60a3031715SAlexander Langer double factor; 619c95bc1cSDavid Malone double offset; 629c95bc1cSDavid Malone int quantity; 63a3031715SAlexander Langer }; 64a3031715SAlexander Langer 6590a29505SEd Schouten static struct { 66a3031715SAlexander Langer char *prefixname; 67a3031715SAlexander Langer char *prefixval; 68a3031715SAlexander Langer } prefixtable[MAXPREFIXES]; 69a3031715SAlexander Langer 70a3031715SAlexander Langer 7190a29505SEd Schouten static char NULLUNIT[] = ""; 72a3031715SAlexander Langer 73ffd044a6SKris Kennaway #define SEPARATOR ":" 74ffd044a6SKris Kennaway 7590a29505SEd Schouten static int unitcount; 7690a29505SEd Schouten static int prefixcount; 779653775eSEitan Adler static bool verbose = false; 788d61f393SEitan Adler static bool terse = false; 79cae14847SEitan Adler static const char * outputformat; 809653775eSEitan Adler static const char * havestr; 819653775eSEitan Adler static const char * wantstr; 829653775eSEitan Adler 83dc8c916dSEitan Adler static int addsubunit(char *product[], char *toadd); 84dc8c916dSEitan Adler static int addunit(struct unittype *theunit, const char *toadd, int flip, int quantity); 85dc8c916dSEitan Adler static void cancelunit(struct unittype * theunit); 86dc8c916dSEitan Adler static int compare(const void *item1, const void *item2); 87dc8c916dSEitan Adler static int compareproducts(char **one, char **two); 88dc8c916dSEitan Adler static int compareunits(struct unittype * first, struct unittype * second); 89dc8c916dSEitan Adler static int completereduce(struct unittype * unit); 90dc8c916dSEitan Adler static char *dupstr(const char *str); 91dc8c916dSEitan Adler static void initializeunit(struct unittype * theunit); 92dc8c916dSEitan Adler static char *lookupunit(const char *unit); 93dc8c916dSEitan Adler static void readunits(const char *userfile); 94dc8c916dSEitan Adler static int reduceproduct(struct unittype * theunit, int flip); 95dc8c916dSEitan Adler static int reduceunit(struct unittype * theunit); 96dc8c916dSEitan Adler static void showanswer(struct unittype * have, struct unittype * want); 97dc8c916dSEitan Adler static void showunit(struct unittype * theunit); 98dc8c916dSEitan Adler static void sortunit(struct unittype * theunit); 99dc8c916dSEitan Adler static void usage(void); 100dc8c916dSEitan Adler static void zeroerror(void); 101a3031715SAlexander Langer 102647be2c3SEitan Adler static const char* promptstr = ""; 103647be2c3SEitan Adler 104647be2c3SEitan Adler static const char * prompt(EditLine *e __unused) { 105647be2c3SEitan Adler return promptstr; 106647be2c3SEitan Adler } 107647be2c3SEitan Adler 1081dd5886fSEitan Adler static char * 10917ad860fSDavid Malone dupstr(const char *str) 110a3031715SAlexander Langer { 111a3031715SAlexander Langer char *ret; 112a3031715SAlexander Langer 113cfa8c236SEitan Adler ret = strdup(str); 114d4d0763aSPhilippe Charnier if (!ret) 115cfa8c236SEitan Adler err(3, "dupstr"); 116a3031715SAlexander Langer return (ret); 117a3031715SAlexander Langer } 118a3031715SAlexander Langer 119a3031715SAlexander Langer 1201dd5886fSEitan Adler static void 12117ad860fSDavid Malone readunits(const char *userfile) 122a3031715SAlexander Langer { 123a3031715SAlexander Langer FILE *unitfile; 12417ad860fSDavid Malone char line[512], *lineptr; 125a3031715SAlexander Langer int len, linenum, i; 126ac117d5dSEitan Adler cap_rights_t unitfilerights; 127a3031715SAlexander Langer 128a3031715SAlexander Langer unitcount = 0; 129a3031715SAlexander Langer linenum = 0; 130a3031715SAlexander Langer 131a3031715SAlexander Langer if (userfile) { 132c706c470SEitan Adler unitfile = fopen(userfile, "r"); 133d4d0763aSPhilippe Charnier if (!unitfile) 134d4d0763aSPhilippe Charnier errx(1, "unable to open units file '%s'", userfile); 135a3031715SAlexander Langer } 136a3031715SAlexander Langer else { 137c706c470SEitan Adler unitfile = fopen(UNITSFILE, "r"); 138a3031715SAlexander Langer if (!unitfile) { 139a3031715SAlexander Langer char *direc, *env; 140a3031715SAlexander Langer char filename[1000]; 141a3031715SAlexander Langer 142a3031715SAlexander Langer env = getenv("PATH"); 143a3031715SAlexander Langer if (env) { 144ffd044a6SKris Kennaway direc = strtok(env, SEPARATOR); 145a3031715SAlexander Langer while (direc) { 146ffd044a6SKris Kennaway snprintf(filename, sizeof(filename), 147ffd044a6SKris Kennaway "%s/%s", direc, UNITSFILE); 148a3031715SAlexander Langer unitfile = fopen(filename, "rt"); 149a3031715SAlexander Langer if (unitfile) 150a3031715SAlexander Langer break; 151ffd044a6SKris Kennaway direc = strtok(NULL, SEPARATOR); 152a3031715SAlexander Langer } 153a3031715SAlexander Langer } 154d4d0763aSPhilippe Charnier if (!unitfile) 155d4d0763aSPhilippe Charnier errx(1, "can't find units file '%s'", UNITSFILE); 156a3031715SAlexander Langer } 157a3031715SAlexander Langer } 158ac117d5dSEitan Adler cap_rights_init(&unitfilerights, CAP_READ, CAP_FSTAT); 159377421dfSMariusz Zaborski if (caph_rights_limit(fileno(unitfile), &unitfilerights) < 0) 160ac117d5dSEitan Adler err(1, "cap_rights_limit() failed"); 161a3031715SAlexander Langer while (!feof(unitfile)) { 16217ad860fSDavid Malone if (!fgets(line, sizeof(line), unitfile)) 163a3031715SAlexander Langer break; 164a3031715SAlexander Langer linenum++; 165a3031715SAlexander Langer lineptr = line; 1660ce97bdfSEitan Adler if (*lineptr == '/' || *lineptr == '#') 167a3031715SAlexander Langer continue; 168a3031715SAlexander Langer lineptr += strspn(lineptr, " \n\t"); 169a3031715SAlexander Langer len = strcspn(lineptr, " \n\t"); 170a3031715SAlexander Langer lineptr[len] = 0; 171a3031715SAlexander Langer if (!strlen(lineptr)) 172a3031715SAlexander Langer continue; 173a3031715SAlexander Langer if (lineptr[strlen(lineptr) - 1] == '-') { /* it's a prefix */ 174a3031715SAlexander Langer if (prefixcount == MAXPREFIXES) { 175d4d0763aSPhilippe Charnier warnx("memory for prefixes exceeded in line %d", linenum); 176a3031715SAlexander Langer continue; 177a3031715SAlexander Langer } 178a3031715SAlexander Langer lineptr[strlen(lineptr) - 1] = 0; 179a3031715SAlexander Langer prefixtable[prefixcount].prefixname = dupstr(lineptr); 180a3031715SAlexander Langer for (i = 0; i < prefixcount; i++) 181a3031715SAlexander Langer if (!strcmp(prefixtable[i].prefixname, lineptr)) { 182d4d0763aSPhilippe Charnier warnx("redefinition of prefix '%s' on line %d ignored", 183a3031715SAlexander Langer lineptr, linenum); 184a3031715SAlexander Langer continue; 185a3031715SAlexander Langer } 186a3031715SAlexander Langer lineptr += len + 1; 187a3031715SAlexander Langer lineptr += strspn(lineptr, " \n\t"); 188a3031715SAlexander Langer len = strcspn(lineptr, "\n\t"); 18917ad860fSDavid Malone if (len == 0) { 19017ad860fSDavid Malone warnx("unexpected end of prefix on line %d", 19117ad860fSDavid Malone linenum); 19217ad860fSDavid Malone continue; 19317ad860fSDavid Malone } 194a3031715SAlexander Langer lineptr[len] = 0; 195a3031715SAlexander Langer prefixtable[prefixcount++].prefixval = dupstr(lineptr); 196a3031715SAlexander Langer } 197a3031715SAlexander Langer else { /* it's not a prefix */ 198a3031715SAlexander Langer if (unitcount == MAXUNITS) { 199d4d0763aSPhilippe Charnier warnx("memory for units exceeded in line %d", linenum); 200a3031715SAlexander Langer continue; 201a3031715SAlexander Langer } 202a3031715SAlexander Langer unittable[unitcount].uname = dupstr(lineptr); 203a3031715SAlexander Langer for (i = 0; i < unitcount; i++) 204a3031715SAlexander Langer if (!strcmp(unittable[i].uname, lineptr)) { 205d4d0763aSPhilippe Charnier warnx("redefinition of unit '%s' on line %d ignored", 206a3031715SAlexander Langer lineptr, linenum); 207a3031715SAlexander Langer continue; 208a3031715SAlexander Langer } 209a3031715SAlexander Langer lineptr += len + 1; 210a3031715SAlexander Langer lineptr += strspn(lineptr, " \n\t"); 211a3031715SAlexander Langer if (!strlen(lineptr)) { 21217ad860fSDavid Malone warnx("unexpected end of unit on line %d", 21317ad860fSDavid Malone linenum); 214a3031715SAlexander Langer continue; 215a3031715SAlexander Langer } 216a3031715SAlexander Langer len = strcspn(lineptr, "\n\t"); 217a3031715SAlexander Langer lineptr[len] = 0; 218a3031715SAlexander Langer unittable[unitcount++].uval = dupstr(lineptr); 219a3031715SAlexander Langer } 220a3031715SAlexander Langer } 221a3031715SAlexander Langer fclose(unitfile); 222a3031715SAlexander Langer } 223a3031715SAlexander Langer 2241dd5886fSEitan Adler static void 225a3031715SAlexander Langer initializeunit(struct unittype * theunit) 226a3031715SAlexander Langer { 227a3031715SAlexander Langer theunit->numerator[0] = theunit->denominator[0] = NULL; 2289c95bc1cSDavid Malone theunit->factor = 1.0; 2299c95bc1cSDavid Malone theunit->offset = 0.0; 2309c95bc1cSDavid Malone theunit->quantity = 0; 231a3031715SAlexander Langer } 232a3031715SAlexander Langer 233a3031715SAlexander Langer 2341dd5886fSEitan Adler static int 235a3031715SAlexander Langer addsubunit(char *product[], char *toadd) 236a3031715SAlexander Langer { 237a3031715SAlexander Langer char **ptr; 238a3031715SAlexander Langer 239a3031715SAlexander Langer for (ptr = product; *ptr && *ptr != NULLUNIT; ptr++); 240a3031715SAlexander Langer if (ptr >= product + MAXSUBUNITS) { 241d4d0763aSPhilippe Charnier warnx("memory overflow in unit reduction"); 242a3031715SAlexander Langer return 1; 243a3031715SAlexander Langer } 244a3031715SAlexander Langer if (!*ptr) 245956c78c9SEitan Adler *(ptr + 1) = NULL; 246a3031715SAlexander Langer *ptr = dupstr(toadd); 247a3031715SAlexander Langer return 0; 248a3031715SAlexander Langer } 249a3031715SAlexander Langer 250a3031715SAlexander Langer 2511dd5886fSEitan Adler static void 252a3031715SAlexander Langer showunit(struct unittype * theunit) 253a3031715SAlexander Langer { 254a3031715SAlexander Langer char **ptr; 255a3031715SAlexander Langer int printedslash; 256a3031715SAlexander Langer int counter = 1; 257a3031715SAlexander Langer 258c706c470SEitan Adler printf(numfmt, theunit->factor); 2599c95bc1cSDavid Malone if (theunit->offset) 2609c95bc1cSDavid Malone printf("&%.8g", theunit->offset); 261a3031715SAlexander Langer for (ptr = theunit->numerator; *ptr; ptr++) { 262a3031715SAlexander Langer if (ptr > theunit->numerator && **ptr && 263a3031715SAlexander Langer !strcmp(*ptr, *(ptr - 1))) 264a3031715SAlexander Langer counter++; 265a3031715SAlexander Langer else { 266a3031715SAlexander Langer if (counter > 1) 267a3031715SAlexander Langer printf("%s%d", powerstring, counter); 268a3031715SAlexander Langer if (**ptr) 269a3031715SAlexander Langer printf(" %s", *ptr); 270a3031715SAlexander Langer counter = 1; 271a3031715SAlexander Langer } 272a3031715SAlexander Langer } 273a3031715SAlexander Langer if (counter > 1) 274a3031715SAlexander Langer printf("%s%d", powerstring, counter); 275a3031715SAlexander Langer counter = 1; 276a3031715SAlexander Langer printedslash = 0; 277a3031715SAlexander Langer for (ptr = theunit->denominator; *ptr; ptr++) { 278a3031715SAlexander Langer if (ptr > theunit->denominator && **ptr && 279a3031715SAlexander Langer !strcmp(*ptr, *(ptr - 1))) 280a3031715SAlexander Langer counter++; 281a3031715SAlexander Langer else { 282a3031715SAlexander Langer if (counter > 1) 283a3031715SAlexander Langer printf("%s%d", powerstring, counter); 284a3031715SAlexander Langer if (**ptr) { 285a3031715SAlexander Langer if (!printedslash) 286a3031715SAlexander Langer printf(" /"); 287a3031715SAlexander Langer printedslash = 1; 288a3031715SAlexander Langer printf(" %s", *ptr); 289a3031715SAlexander Langer } 290a3031715SAlexander Langer counter = 1; 291a3031715SAlexander Langer } 292a3031715SAlexander Langer } 293a3031715SAlexander Langer if (counter > 1) 294a3031715SAlexander Langer printf("%s%d", powerstring, counter); 295a3031715SAlexander Langer printf("\n"); 296a3031715SAlexander Langer } 297a3031715SAlexander Langer 298a3031715SAlexander Langer 299a3031715SAlexander Langer void 30017ad860fSDavid Malone zeroerror(void) 301a3031715SAlexander Langer { 302d4d0763aSPhilippe Charnier warnx("unit reduces to zero"); 303a3031715SAlexander Langer } 304a3031715SAlexander Langer 305a3031715SAlexander Langer /* 306a3031715SAlexander Langer Adds the specified string to the unit. 307a3031715SAlexander Langer Flip is 0 for adding normally, 1 for adding reciprocal. 3089c95bc1cSDavid Malone Quantity is 1 if this is a quantity to be converted rather than a pure unit. 309a3031715SAlexander Langer 310a3031715SAlexander Langer Returns 0 for successful addition, nonzero on error. 311a3031715SAlexander Langer */ 312a3031715SAlexander Langer 3131dd5886fSEitan Adler static int 314647be2c3SEitan Adler addunit(struct unittype * theunit, const char *toadd, int flip, int quantity) 315a3031715SAlexander Langer { 316a3031715SAlexander Langer char *scratch, *savescr; 317a3031715SAlexander Langer char *item; 3189c95bc1cSDavid Malone char *divider, *slash, *offset; 319a3031715SAlexander Langer int doingtop; 320a3031715SAlexander Langer 321ffd044a6SKris Kennaway if (!strlen(toadd)) 322ffd044a6SKris Kennaway return 1; 323ffd044a6SKris Kennaway 324a3031715SAlexander Langer savescr = scratch = dupstr(toadd); 325a3031715SAlexander Langer for (slash = scratch + 1; *slash; slash++) 326a3031715SAlexander Langer if (*slash == '-' && 327a3031715SAlexander Langer (tolower(*(slash - 1)) != 'e' || 328a3031715SAlexander Langer !strchr(".0123456789", *(slash + 1)))) 329a3031715SAlexander Langer *slash = ' '; 330a3031715SAlexander Langer slash = strchr(scratch, '/'); 331a3031715SAlexander Langer if (slash) 332a3031715SAlexander Langer *slash = 0; 333a3031715SAlexander Langer doingtop = 1; 334a3031715SAlexander Langer do { 335a3031715SAlexander Langer item = strtok(scratch, " *\t\n/"); 336a3031715SAlexander Langer while (item) { 337a3031715SAlexander Langer if (strchr("0123456789.", *item)) { /* item is a number */ 3389c95bc1cSDavid Malone double num, offsetnum; 3399c95bc1cSDavid Malone 3409c95bc1cSDavid Malone if (quantity) 3419c95bc1cSDavid Malone theunit->quantity = 1; 3429c95bc1cSDavid Malone 3439c95bc1cSDavid Malone offset = strchr(item, '&'); 3449c95bc1cSDavid Malone if (offset) { 3459c95bc1cSDavid Malone *offset = 0; 3469c95bc1cSDavid Malone offsetnum = atof(offset+1); 3479c95bc1cSDavid Malone } else 3489c95bc1cSDavid Malone offsetnum = 0.0; 349a3031715SAlexander Langer 350a3031715SAlexander Langer divider = strchr(item, '|'); 351a3031715SAlexander Langer if (divider) { 352a3031715SAlexander Langer *divider = 0; 353a3031715SAlexander Langer num = atof(item); 354a3031715SAlexander Langer if (!num) { 355a3031715SAlexander Langer zeroerror(); 3569cd768a1SEitan Adler free(savescr); 357a3031715SAlexander Langer return 1; 358a3031715SAlexander Langer } 3599c95bc1cSDavid Malone if (doingtop ^ flip) { 360a3031715SAlexander Langer theunit->factor *= num; 3619c95bc1cSDavid Malone theunit->offset *= num; 3629c95bc1cSDavid Malone } else { 363a3031715SAlexander Langer theunit->factor /= num; 3649c95bc1cSDavid Malone theunit->offset /= num; 3659c95bc1cSDavid Malone } 366a3031715SAlexander Langer num = atof(divider + 1); 367a3031715SAlexander Langer if (!num) { 368a3031715SAlexander Langer zeroerror(); 3699cd768a1SEitan Adler free(savescr); 370a3031715SAlexander Langer return 1; 371a3031715SAlexander Langer } 3729c95bc1cSDavid Malone if (doingtop ^ flip) { 373a3031715SAlexander Langer theunit->factor /= num; 3749c95bc1cSDavid Malone theunit->offset /= num; 3759c95bc1cSDavid Malone } else { 376a3031715SAlexander Langer theunit->factor *= num; 3779c95bc1cSDavid Malone theunit->offset *= num; 3789c95bc1cSDavid Malone } 379a3031715SAlexander Langer } 380a3031715SAlexander Langer else { 381a3031715SAlexander Langer num = atof(item); 382a3031715SAlexander Langer if (!num) { 383a3031715SAlexander Langer zeroerror(); 3849cd768a1SEitan Adler free(savescr); 385a3031715SAlexander Langer return 1; 386a3031715SAlexander Langer } 3879c95bc1cSDavid Malone if (doingtop ^ flip) { 388a3031715SAlexander Langer theunit->factor *= num; 3899c95bc1cSDavid Malone theunit->offset *= num; 3909c95bc1cSDavid Malone } else { 391a3031715SAlexander Langer theunit->factor /= num; 3929c95bc1cSDavid Malone theunit->offset /= num; 393a3031715SAlexander Langer } 394a3031715SAlexander Langer } 3959c95bc1cSDavid Malone if (doingtop ^ flip) 3969c95bc1cSDavid Malone theunit->offset += offsetnum; 3979c95bc1cSDavid Malone } 398a3031715SAlexander Langer else { /* item is not a number */ 399a3031715SAlexander Langer int repeat = 1; 400a3031715SAlexander Langer 401a3031715SAlexander Langer if (strchr("23456789", 402a3031715SAlexander Langer item[strlen(item) - 1])) { 403a3031715SAlexander Langer repeat = item[strlen(item) - 1] - '0'; 404a3031715SAlexander Langer item[strlen(item) - 1] = 0; 405a3031715SAlexander Langer } 4069cd768a1SEitan Adler for (; repeat; repeat--) { 4079cd768a1SEitan Adler if (addsubunit(doingtop ^ flip ? theunit->numerator : theunit->denominator, item)) { 4089cd768a1SEitan Adler free(savescr); 409a3031715SAlexander Langer return 1; 410a3031715SAlexander Langer } 4119cd768a1SEitan Adler } 4129cd768a1SEitan Adler } 413a3031715SAlexander Langer item = strtok(NULL, " *\t/\n"); 414a3031715SAlexander Langer } 415a3031715SAlexander Langer doingtop--; 416a3031715SAlexander Langer if (slash) { 417a3031715SAlexander Langer scratch = slash + 1; 418a3031715SAlexander Langer } 419a3031715SAlexander Langer else 420a3031715SAlexander Langer doingtop--; 421a3031715SAlexander Langer } while (doingtop >= 0); 422a3031715SAlexander Langer free(savescr); 423a3031715SAlexander Langer return 0; 424a3031715SAlexander Langer } 425a3031715SAlexander Langer 426a3031715SAlexander Langer 4271dd5886fSEitan Adler static int 428a3031715SAlexander Langer compare(const void *item1, const void *item2) 429a3031715SAlexander Langer { 43050bb7724SDavid Malone return strcmp(*(const char * const *)item1, *(const char * const *)item2); 431a3031715SAlexander Langer } 432a3031715SAlexander Langer 433a3031715SAlexander Langer 4341dd5886fSEitan Adler static void 435a3031715SAlexander Langer sortunit(struct unittype * theunit) 436a3031715SAlexander Langer { 437a3031715SAlexander Langer char **ptr; 43817ad860fSDavid Malone unsigned int count; 439a3031715SAlexander Langer 440a3031715SAlexander Langer for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++); 441a3031715SAlexander Langer qsort(theunit->numerator, count, sizeof(char *), compare); 442a3031715SAlexander Langer for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++); 443a3031715SAlexander Langer qsort(theunit->denominator, count, sizeof(char *), compare); 444a3031715SAlexander Langer } 445a3031715SAlexander Langer 446a3031715SAlexander Langer 447a3031715SAlexander Langer void 448a3031715SAlexander Langer cancelunit(struct unittype * theunit) 449a3031715SAlexander Langer { 450a3031715SAlexander Langer char **den, **num; 451a3031715SAlexander Langer int comp; 452a3031715SAlexander Langer 453a3031715SAlexander Langer den = theunit->denominator; 454a3031715SAlexander Langer num = theunit->numerator; 455a3031715SAlexander Langer 456a3031715SAlexander Langer while (*num && *den) { 457a3031715SAlexander Langer comp = strcmp(*den, *num); 458a3031715SAlexander Langer if (!comp) { 459a3031715SAlexander Langer /* if (*den!=NULLUNIT) free(*den); 460a3031715SAlexander Langer if (*num!=NULLUNIT) free(*num);*/ 461a3031715SAlexander Langer *den++ = NULLUNIT; 462a3031715SAlexander Langer *num++ = NULLUNIT; 463a3031715SAlexander Langer } 464a3031715SAlexander Langer else if (comp < 0) 465a3031715SAlexander Langer den++; 466a3031715SAlexander Langer else 467a3031715SAlexander Langer num++; 468a3031715SAlexander Langer } 469a3031715SAlexander Langer } 470a3031715SAlexander Langer 471a3031715SAlexander Langer 472a3031715SAlexander Langer 473a3031715SAlexander Langer 474a3031715SAlexander Langer /* 475a3031715SAlexander Langer Looks up the definition for the specified unit. 476a3031715SAlexander Langer Returns a pointer to the definition or a null pointer 477a3031715SAlexander Langer if the specified unit does not appear in the units table. 478a3031715SAlexander Langer */ 479a3031715SAlexander Langer 480a3031715SAlexander Langer static char buffer[100]; /* buffer for lookupunit answers with 481a3031715SAlexander Langer prefixes */ 482a3031715SAlexander Langer 483a3031715SAlexander Langer char * 48417ad860fSDavid Malone lookupunit(const char *unit) 485a3031715SAlexander Langer { 486a3031715SAlexander Langer int i; 487a3031715SAlexander Langer char *copy; 488a3031715SAlexander Langer 489a3031715SAlexander Langer for (i = 0; i < unitcount; i++) { 490a3031715SAlexander Langer if (!strcmp(unittable[i].uname, unit)) 491a3031715SAlexander Langer return unittable[i].uval; 492a3031715SAlexander Langer } 493a3031715SAlexander Langer 494a3031715SAlexander Langer if (unit[strlen(unit) - 1] == '^') { 495a3031715SAlexander Langer copy = dupstr(unit); 496a3031715SAlexander Langer copy[strlen(copy) - 1] = 0; 497a3031715SAlexander Langer for (i = 0; i < unitcount; i++) { 498a3031715SAlexander Langer if (!strcmp(unittable[i].uname, copy)) { 499ffd044a6SKris Kennaway strlcpy(buffer, copy, sizeof(buffer)); 500a3031715SAlexander Langer free(copy); 501a3031715SAlexander Langer return buffer; 502a3031715SAlexander Langer } 503a3031715SAlexander Langer } 504a3031715SAlexander Langer free(copy); 505a3031715SAlexander Langer } 506a3031715SAlexander Langer if (unit[strlen(unit) - 1] == 's') { 507a3031715SAlexander Langer copy = dupstr(unit); 508a3031715SAlexander Langer copy[strlen(copy) - 1] = 0; 509a3031715SAlexander Langer for (i = 0; i < unitcount; i++) { 510a3031715SAlexander Langer if (!strcmp(unittable[i].uname, copy)) { 511ffd044a6SKris Kennaway strlcpy(buffer, copy, sizeof(buffer)); 512a3031715SAlexander Langer free(copy); 513a3031715SAlexander Langer return buffer; 514a3031715SAlexander Langer } 515a3031715SAlexander Langer } 516a3031715SAlexander Langer if (copy[strlen(copy) - 1] == 'e') { 517a3031715SAlexander Langer copy[strlen(copy) - 1] = 0; 518a3031715SAlexander Langer for (i = 0; i < unitcount; i++) { 519a3031715SAlexander Langer if (!strcmp(unittable[i].uname, copy)) { 520ffd044a6SKris Kennaway strlcpy(buffer, copy, sizeof(buffer)); 521a3031715SAlexander Langer free(copy); 522a3031715SAlexander Langer return buffer; 523a3031715SAlexander Langer } 524a3031715SAlexander Langer } 525a3031715SAlexander Langer } 526a3031715SAlexander Langer free(copy); 527a3031715SAlexander Langer } 528a3031715SAlexander Langer for (i = 0; i < prefixcount; i++) { 52917ad860fSDavid Malone size_t len = strlen(prefixtable[i].prefixname); 53017ad860fSDavid Malone if (!strncmp(prefixtable[i].prefixname, unit, len)) { 53117ad860fSDavid Malone if (!strlen(unit + len) || lookupunit(unit + len)) { 532ffd044a6SKris Kennaway snprintf(buffer, sizeof(buffer), "%s %s", 53317ad860fSDavid Malone prefixtable[i].prefixval, unit + len); 534a3031715SAlexander Langer return buffer; 535a3031715SAlexander Langer } 536a3031715SAlexander Langer } 537a3031715SAlexander Langer } 538a3031715SAlexander Langer return 0; 539a3031715SAlexander Langer } 540a3031715SAlexander Langer 541a3031715SAlexander Langer 542a3031715SAlexander Langer 543a3031715SAlexander Langer /* 544a3031715SAlexander Langer reduces a product of symbolic units to primitive units. 545a3031715SAlexander Langer The three low bits are used to return flags: 546a3031715SAlexander Langer 547a3031715SAlexander Langer bit 0 (1) set on if reductions were performed without error. 548a3031715SAlexander Langer bit 1 (2) set on if no reductions are performed. 549a3031715SAlexander Langer bit 2 (4) set on if an unknown unit is discovered. 550a3031715SAlexander Langer */ 551a3031715SAlexander Langer 552a3031715SAlexander Langer 553a3031715SAlexander Langer #define ERROR 4 554a3031715SAlexander Langer 5551dd5886fSEitan Adler static int 556a3031715SAlexander Langer reduceproduct(struct unittype * theunit, int flip) 557a3031715SAlexander Langer { 558a3031715SAlexander Langer 559a3031715SAlexander Langer char *toadd; 560a3031715SAlexander Langer char **product; 561a3031715SAlexander Langer int didsomething = 2; 562a3031715SAlexander Langer 563a3031715SAlexander Langer if (flip) 564a3031715SAlexander Langer product = theunit->denominator; 565a3031715SAlexander Langer else 566a3031715SAlexander Langer product = theunit->numerator; 567a3031715SAlexander Langer 568a3031715SAlexander Langer for (; *product; product++) { 569a3031715SAlexander Langer 570a3031715SAlexander Langer for (;;) { 571a3031715SAlexander Langer if (!strlen(*product)) 572a3031715SAlexander Langer break; 573a3031715SAlexander Langer toadd = lookupunit(*product); 574a3031715SAlexander Langer if (!toadd) { 575a3031715SAlexander Langer printf("unknown unit '%s'\n", *product); 576a3031715SAlexander Langer return ERROR; 577a3031715SAlexander Langer } 578a3031715SAlexander Langer if (strchr(toadd, PRIMITIVECHAR)) 579a3031715SAlexander Langer break; 580a3031715SAlexander Langer didsomething = 1; 581a3031715SAlexander Langer if (*product != NULLUNIT) { 582a3031715SAlexander Langer free(*product); 583a3031715SAlexander Langer *product = NULLUNIT; 584a3031715SAlexander Langer } 5859c95bc1cSDavid Malone if (addunit(theunit, toadd, flip, 0)) 586a3031715SAlexander Langer return ERROR; 587a3031715SAlexander Langer } 588a3031715SAlexander Langer } 589a3031715SAlexander Langer return didsomething; 590a3031715SAlexander Langer } 591a3031715SAlexander Langer 592a3031715SAlexander Langer 593a3031715SAlexander Langer /* 594a3031715SAlexander Langer Reduces numerator and denominator of the specified unit. 595a3031715SAlexander Langer Returns 0 on success, or 1 on unknown unit error. 596a3031715SAlexander Langer */ 597a3031715SAlexander Langer 5981dd5886fSEitan Adler static int 599a3031715SAlexander Langer reduceunit(struct unittype * theunit) 600a3031715SAlexander Langer { 601a3031715SAlexander Langer int ret; 602a3031715SAlexander Langer 603a3031715SAlexander Langer ret = 1; 604a3031715SAlexander Langer while (ret & 1) { 605a3031715SAlexander Langer ret = reduceproduct(theunit, 0) | reduceproduct(theunit, 1); 606a3031715SAlexander Langer if (ret & 4) 607a3031715SAlexander Langer return 1; 608a3031715SAlexander Langer } 609a3031715SAlexander Langer return 0; 610a3031715SAlexander Langer } 611a3031715SAlexander Langer 612a3031715SAlexander Langer 6131dd5886fSEitan Adler static int 614a3031715SAlexander Langer compareproducts(char **one, char **two) 615a3031715SAlexander Langer { 616a3031715SAlexander Langer while (*one || *two) { 617a3031715SAlexander Langer if (!*one && *two != NULLUNIT) 618a3031715SAlexander Langer return 1; 619a3031715SAlexander Langer if (!*two && *one != NULLUNIT) 620a3031715SAlexander Langer return 1; 621a3031715SAlexander Langer if (*one == NULLUNIT) 622a3031715SAlexander Langer one++; 623a3031715SAlexander Langer else if (*two == NULLUNIT) 624a3031715SAlexander Langer two++; 625a3031715SAlexander Langer else if (strcmp(*one, *two)) 626a3031715SAlexander Langer return 1; 627943a127cSEitan Adler else { 628943a127cSEitan Adler one++; 629943a127cSEitan Adler two++; 630943a127cSEitan Adler } 631a3031715SAlexander Langer } 632a3031715SAlexander Langer return 0; 633a3031715SAlexander Langer } 634a3031715SAlexander Langer 635a3031715SAlexander Langer 636a3031715SAlexander Langer /* Return zero if units are compatible, nonzero otherwise */ 637a3031715SAlexander Langer 6381dd5886fSEitan Adler static int 639a3031715SAlexander Langer compareunits(struct unittype * first, struct unittype * second) 640a3031715SAlexander Langer { 641a3031715SAlexander Langer return 642a3031715SAlexander Langer compareproducts(first->numerator, second->numerator) || 643a3031715SAlexander Langer compareproducts(first->denominator, second->denominator); 644a3031715SAlexander Langer } 645a3031715SAlexander Langer 646a3031715SAlexander Langer 6471dd5886fSEitan Adler static int 648a3031715SAlexander Langer completereduce(struct unittype * unit) 649a3031715SAlexander Langer { 650a3031715SAlexander Langer if (reduceunit(unit)) 651a3031715SAlexander Langer return 1; 652a3031715SAlexander Langer sortunit(unit); 653a3031715SAlexander Langer cancelunit(unit); 654a3031715SAlexander Langer return 0; 655a3031715SAlexander Langer } 656a3031715SAlexander Langer 6571dd5886fSEitan Adler static void 658a3031715SAlexander Langer showanswer(struct unittype * have, struct unittype * want) 659a3031715SAlexander Langer { 6609653775eSEitan Adler double ans; 661cae14847SEitan Adler char* oformat; 6629653775eSEitan Adler 663a3031715SAlexander Langer if (compareunits(have, want)) { 664a3031715SAlexander Langer printf("conformability error\n"); 6659653775eSEitan Adler if (verbose) 6669653775eSEitan Adler printf("\t%s = ", havestr); 6678d61f393SEitan Adler else if (!terse) 6689653775eSEitan Adler printf("\t"); 669a3031715SAlexander Langer showunit(have); 6708d61f393SEitan Adler if (!terse) { 6719653775eSEitan Adler if (verbose) 6729653775eSEitan Adler printf("\t%s = ", wantstr); 6739653775eSEitan Adler else 6749653775eSEitan Adler printf("\t"); 675a3031715SAlexander Langer showunit(want); 676a3031715SAlexander Langer } 6778d61f393SEitan Adler } 6789c95bc1cSDavid Malone else if (have->offset != want->offset) { 6799c95bc1cSDavid Malone if (want->quantity) 6809c95bc1cSDavid Malone printf("WARNING: conversion of non-proportional quantities.\n"); 681cae14847SEitan Adler if (have->quantity) { 682cae14847SEitan Adler asprintf(&oformat, "\t%s\n", outputformat); 683cae14847SEitan Adler printf(oformat, 6849c95bc1cSDavid Malone (have->factor + have->offset-want->offset)/want->factor); 685cae14847SEitan Adler free(oformat); 686cae14847SEitan Adler } 6879653775eSEitan Adler else { 688cae14847SEitan Adler asprintf(&oformat, "\t (-> x*%sg %sg)\n\t (<- y*%sg %sg)\n", 689cae14847SEitan Adler outputformat, outputformat, outputformat, outputformat); 690cae14847SEitan Adler printf(oformat, 6919c95bc1cSDavid Malone have->factor / want->factor, 6929c95bc1cSDavid Malone (have->offset-want->offset)/want->factor, 6939c95bc1cSDavid Malone want->factor / have->factor, 6949c95bc1cSDavid Malone (want->offset - have->offset)/have->factor); 6959c95bc1cSDavid Malone } 6969653775eSEitan Adler } 6979653775eSEitan Adler else { 6989653775eSEitan Adler ans = have->factor / want->factor; 6999653775eSEitan Adler 700cae14847SEitan Adler if (verbose) { 701cae14847SEitan Adler printf("\t%s = ", havestr); 702cae14847SEitan Adler printf(outputformat, ans); 703cae14847SEitan Adler printf(" * %s", wantstr); 704cae14847SEitan Adler printf("\n"); 705cae14847SEitan Adler } 706cae14847SEitan Adler else if (terse) { 707cae14847SEitan Adler printf(outputformat, ans); 708cae14847SEitan Adler printf("\n"); 709cae14847SEitan Adler } 710cae14847SEitan Adler else { 711cae14847SEitan Adler printf("\t* "); 712cae14847SEitan Adler printf(outputformat, ans); 713cae14847SEitan Adler printf("\n"); 714cae14847SEitan Adler } 715cae14847SEitan Adler 716cae14847SEitan Adler if (verbose) { 717cae14847SEitan Adler printf("\t%s = (1 / ", havestr); 718cae14847SEitan Adler printf(outputformat, 1/ans); 719cae14847SEitan Adler printf(") * %s\n", wantstr); 720cae14847SEitan Adler } 721cae14847SEitan Adler else if (!terse) { 722cae14847SEitan Adler printf("\t/ "); 723cae14847SEitan Adler printf(outputformat, 1/ans); 724cae14847SEitan Adler printf("\n"); 725cae14847SEitan Adler } 7269653775eSEitan Adler } 727a3031715SAlexander Langer } 728a3031715SAlexander Langer 729a3031715SAlexander Langer 730943a127cSEitan Adler static void __dead2 73117ad860fSDavid Malone usage(void) 732a3031715SAlexander Langer { 733d4d0763aSPhilippe Charnier fprintf(stderr, 734*4a3b87e2SMateusz Piotrowski "usage: units [-ehqtUVv] [-f unitsfile] [-o format] [from to]\n"); 735a3031715SAlexander Langer exit(3); 736a3031715SAlexander Langer } 737a3031715SAlexander Langer 7385192ff45SEitan Adler static struct option longopts[] = { 739cae14847SEitan Adler {"exponential", no_argument, NULL, 'e'}, 7405192ff45SEitan Adler {"file", required_argument, NULL, 'f'}, 741c706c470SEitan Adler {"history", required_argument, NULL, 'H'}, 742*4a3b87e2SMateusz Piotrowski {"help", no_argument, NULL, 'h'}, 743cae14847SEitan Adler {"output-format", required_argument, NULL, 'o'}, 7445192ff45SEitan Adler {"quiet", no_argument, NULL, 'q'}, 7458d61f393SEitan Adler {"terse", no_argument, NULL, 't'}, 7465192ff45SEitan Adler {"unitsfile", no_argument, NULL, 'U'}, 7475192ff45SEitan Adler {"version", no_argument, NULL, 'V'}, 748*4a3b87e2SMateusz Piotrowski {"verbose", no_argument, NULL, 'v'}, 7495192ff45SEitan Adler { 0, 0, 0, 0 } 7505192ff45SEitan Adler }; 7515192ff45SEitan Adler 752a3031715SAlexander Langer 753a3031715SAlexander Langer int 754a3031715SAlexander Langer main(int argc, char **argv) 755a3031715SAlexander Langer { 756a3031715SAlexander Langer 757a3031715SAlexander Langer struct unittype have, want; 758a3031715SAlexander Langer int optchar; 759647be2c3SEitan Adler bool quiet; 7603af08201SEitan Adler bool readfile; 761c706c470SEitan Adler bool quit; 762647be2c3SEitan Adler History *inhistory; 763647be2c3SEitan Adler EditLine *el; 764647be2c3SEitan Adler HistEvent ev; 765647be2c3SEitan Adler int inputsz; 766c706c470SEitan Adler char const * history_file; 767a3031715SAlexander Langer 768647be2c3SEitan Adler quiet = false; 7693af08201SEitan Adler readfile = false; 770c706c470SEitan Adler history_file = NULL; 771c706c470SEitan Adler outputformat = numfmt; 772c706c470SEitan Adler quit = false; 77321dc7ae7SEitan Adler while ((optchar = getopt_long(argc, argv, "+ehf:o:qtvH:UV", longopts, NULL)) != -1) { 774a3031715SAlexander Langer switch (optchar) { 775cae14847SEitan Adler case 'e': 776cae14847SEitan Adler outputformat = "%6e"; 777cae14847SEitan Adler break; 778a3031715SAlexander Langer case 'f': 7793af08201SEitan Adler readfile = true; 7803af08201SEitan Adler if (strlen(optarg) == 0) 7813af08201SEitan Adler readunits(NULL); 7823af08201SEitan Adler else 7833af08201SEitan Adler readunits(optarg); 784a3031715SAlexander Langer break; 785c706c470SEitan Adler case 'H': 786c706c470SEitan Adler history_file = optarg; 787c706c470SEitan Adler break; 788a3031715SAlexander Langer case 'q': 789647be2c3SEitan Adler quiet = true; 790a3031715SAlexander Langer break; 7918d61f393SEitan Adler case 't': 7928d61f393SEitan Adler terse = true; 7938d61f393SEitan Adler break; 794cae14847SEitan Adler case 'o': 795cae14847SEitan Adler outputformat = optarg; 796cae14847SEitan Adler break; 7979653775eSEitan Adler case 'v': 7989653775eSEitan Adler verbose = true; 7999653775eSEitan Adler break; 800db9def8bSEitan Adler case 'V': 801db9def8bSEitan Adler fprintf(stderr, "FreeBSD units\n"); 802db9def8bSEitan Adler /* FALLTHROUGH */ 803114022f5SEitan Adler case 'U': 804114022f5SEitan Adler if (access(UNITSFILE, F_OK) == 0) 805114022f5SEitan Adler printf("%s\n", UNITSFILE); 806114022f5SEitan Adler else 807114022f5SEitan Adler printf("Units data file not found"); 808114022f5SEitan Adler exit(0); 809a913717bSEitan Adler case 'h': 810a913717bSEitan Adler /* FALLTHROUGH */ 811a913717bSEitan Adler 8126d12a834SEitan Adler default: 8136d12a834SEitan Adler usage(); 814a3031715SAlexander Langer } 815a3031715SAlexander Langer } 816a3031715SAlexander Langer 8173af08201SEitan Adler if (!readfile) 8183af08201SEitan Adler readunits(NULL); 819a3031715SAlexander Langer 820840a09d2SEdward Tomasz Napierala if (optind == argc - 2) { 8217672a014SMariusz Zaborski if (caph_enter() < 0) 8223af08201SEitan Adler err(1, "unable to enter capability mode"); 823a3031715SAlexander Langer 824647be2c3SEitan Adler havestr = argv[optind]; 825647be2c3SEitan Adler wantstr = argv[optind + 1]; 826a3031715SAlexander Langer initializeunit(&have); 8279c95bc1cSDavid Malone addunit(&have, havestr, 0, 1); 828a3031715SAlexander Langer completereduce(&have); 829a3031715SAlexander Langer initializeunit(&want); 8309c95bc1cSDavid Malone addunit(&want, wantstr, 0, 1); 831a3031715SAlexander Langer completereduce(&want); 832a3031715SAlexander Langer showanswer(&have, &want); 833840a09d2SEdward Tomasz Napierala } else { 834619e4f78SJulio Merino inhistory = history_init(); 835619e4f78SJulio Merino el = el_init(argv[0], stdin, stdout, stderr); 836619e4f78SJulio Merino el_set(el, EL_PROMPT, &prompt); 837619e4f78SJulio Merino el_set(el, EL_EDITOR, "emacs"); 838619e4f78SJulio Merino el_set(el, EL_SIGNAL, 1); 839619e4f78SJulio Merino el_set(el, EL_HIST, history, inhistory); 840619e4f78SJulio Merino el_source(el, NULL); 841619e4f78SJulio Merino history(inhistory, &ev, H_SETSIZE, 800); 842619e4f78SJulio Merino if (inhistory == 0) 843619e4f78SJulio Merino err(1, "Could not initialize history"); 844619e4f78SJulio Merino 8457672a014SMariusz Zaborski if (caph_enter() < 0) 846840a09d2SEdward Tomasz Napierala err(1, "unable to enter capability mode"); 847840a09d2SEdward Tomasz Napierala 848a3031715SAlexander Langer if (!quiet) 849d4d0763aSPhilippe Charnier printf("%d units, %d prefixes\n", unitcount, 850a3031715SAlexander Langer prefixcount); 851c706c470SEitan Adler while (!quit) { 852a3031715SAlexander Langer do { 853a3031715SAlexander Langer initializeunit(&have); 854a3031715SAlexander Langer if (!quiet) 855647be2c3SEitan Adler promptstr = "You have: "; 856647be2c3SEitan Adler havestr = el_gets(el, &inputsz); 857c706c470SEitan Adler if (havestr == NULL) { 858c706c470SEitan Adler quit = true; 859c706c470SEitan Adler break; 860c706c470SEitan Adler } 861647be2c3SEitan Adler if (inputsz > 0) 862647be2c3SEitan Adler history(inhistory, &ev, H_ENTER, 863647be2c3SEitan Adler havestr); 8649c95bc1cSDavid Malone } while (addunit(&have, havestr, 0, 1) || 865a3031715SAlexander Langer completereduce(&have)); 866c706c470SEitan Adler if (quit) { 867c706c470SEitan Adler break; 868c706c470SEitan Adler } 869a3031715SAlexander Langer do { 870a3031715SAlexander Langer initializeunit(&want); 871a3031715SAlexander Langer if (!quiet) 872647be2c3SEitan Adler promptstr = "You want: "; 873647be2c3SEitan Adler wantstr = el_gets(el, &inputsz); 874c706c470SEitan Adler if (wantstr == NULL) { 875c706c470SEitan Adler quit = true; 876c706c470SEitan Adler break; 877c706c470SEitan Adler } 878647be2c3SEitan Adler if (inputsz > 0) 879647be2c3SEitan Adler history(inhistory, &ev, H_ENTER, 880647be2c3SEitan Adler wantstr); 8819c95bc1cSDavid Malone } while (addunit(&want, wantstr, 0, 1) || 882a3031715SAlexander Langer completereduce(&want)); 883c706c470SEitan Adler if (quit) { 884c706c470SEitan Adler break; 885c706c470SEitan Adler } 886a3031715SAlexander Langer showanswer(&have, &want); 887a3031715SAlexander Langer } 8884c0c227dSAlexander Langer 889647be2c3SEitan Adler history_end(inhistory); 890373ca6f9SEitan Adler el_end(el); 891619e4f78SJulio Merino } 892619e4f78SJulio Merino 8934c0c227dSAlexander Langer return (0); 894a3031715SAlexander Langer } 895