| #define VERSION "2.23" |
| /* |
| * units, a program for units conversion |
| * Copyright (C) 1996, 1997, 1999, 2000-2007, 2009, 2011-2020, 2022, 2024 |
| * Free Software Foundation, Inc |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301 USA |
| * |
| * This program was written by Adrian Mariano (adrianm@gnu.org) |
| */ |
| |
| #define LICENSE "\ |
| Copyright (C) 2024 Free Software Foundation, Inc.\n\ |
| GNU Units comes with ABSOLUTELY NO WARRANTY.\n\ |
| You may redistribute copies of GNU Units\n\ |
| under the terms of the GNU General Public License." |
| |
| #if defined (_WIN32) && defined (_MSC_VER) |
| # include <windows.h> |
| # include <winbase.h> |
| #endif |
| #if defined (_WIN32) && defined (HAVE_MKS_TOOLKIT) |
| # include <sys/types.h> |
| #endif |
| # include <sys/stat.h> |
| |
| #include <ctype.h> |
| #include <float.h> |
| |
| #include <string.h> |
| #include <stdio.h> |
| #include <signal.h> |
| #include <stdarg.h> |
| #include <time.h> |
| |
| #if defined (_WIN32) && defined (_MSC_VER) |
| # include <io.h> |
| # define fileno _fileno |
| # define isatty _isatty |
| # define stat _stat |
| #endif |
| |
| #ifdef HAVE_IOCTL |
| # include <sys/ioctl.h> |
| # include <fcntl.h> |
| #endif |
| |
| #ifndef NO_SETLOCALE |
| # include<locale.h> |
| #endif |
| |
| #ifdef SUPPORT_UTF8 |
| /* Apparently this define is needed to get wcswidth() prototype */ |
| # include <wchar.h> |
| # include <langinfo.h> |
| # define UTF8VERSTR "with utf8" |
| #else |
| # define UTF8VERSTR "without utf8" |
| #endif |
| |
| #ifdef READLINE |
| # define RVERSTR "with readline" |
| # include <readline/readline.h> |
| # include <readline/history.h> |
| # define HISTORY_FILE ".units_history" |
| #else |
| # define RVERSTR "without readline" |
| #endif |
| |
| #include "getopt.h" |
| #include "units.h" |
| |
| #ifndef UNITSFILE |
| # define UNITSFILE "definitions.units" |
| #endif |
| |
| #ifndef LOCALEMAP |
| # define LOCALEMAP "locale_map.txt" |
| #endif |
| |
| #ifndef DATADIR |
| # ifdef _WIN32 |
| # define DATADIR "..\\share\\units" |
| # else |
| # define DATADIR "../share/units" |
| # endif |
| #endif |
| |
| #if defined (_WIN32) && defined (_MSC_VER) |
| # include <direct.h> |
| # define getcwd _getcwd |
| #else |
| # include <unistd.h> |
| #endif |
| |
| #ifdef _WIN32 |
| # define EXE_EXT ".exe" |
| # define PATHSEP ';' |
| # define DIRSEP '\\' |
| # define DIRSEPSTR "\\" /* for building pathnames */ |
| #else |
| # define EXE_EXT "" |
| # define PATHSEP ':' |
| # define DIRSEP '/' |
| # define DIRSEPSTR "/" /* for building pathnames */ |
| #endif |
| |
| #define PRIMITIVECHAR '!' /* Character that marks irreducible units */ |
| #define COMMENTCHAR '#' /* Comments marked by this character */ |
| #define COMMANDCHAR '!' /* Unit database commands marked with this */ |
| #define UNITSEPCHAR ';' /* Separator for unit lists. Include this */ |
| /* char in rl_basic_word_break_characters */ |
| /* and in nonunitchars defined in parse.y */ |
| #define FUNCSEPCHAR ';' /* Separates forward and inverse definitions */ |
| #define REDEFCHAR '+' /* Mark unit as redefinition to suppress warning message */ |
| #ifdef _WIN32 |
| # define DEFAULTPAGER "more" /* Default pager for Windows */ |
| #else |
| # define DEFAULTPAGER "/usr/bin/pager" /* Default pager for Unix */ |
| #endif |
| #define DEFAULTLOCALE "en_US" /* Default locale */ |
| #define MAXINCLUDE 5 /* Max depth of include files */ |
| #define MAXFILES 25 /* Max number of units files on command line */ |
| #define NODIM "!dimensionless" /* Marks dimensionless primitive units, such */ |
| /* as the radian, which are ignored when */ |
| /* doing unit comparisons */ |
| #define NOPOINT -1 /* suppress display of pointer in processunit*/ |
| #define NOERRMSG -2 /* no error messages in checkunitlist() */ |
| #define ERRMSG -3 |
| #define SHOWFILES -4 |
| |
| #define MAXHISTORYFILE 5000 /* max length of history file for readline */ |
| |
| #define MAXPRODUCTREDUCTIONS 1000 /* If we make this many reductions, declare */ |
| /* a circular reference */ |
| |
| /* values for output number format */ |
| #define BASE_FORMATS "gGeEf" /* printf() format types recognized pre-C99 */ |
| #define DEFAULTPRECISION 8 /* default significant digits for printf() */ |
| #define DEFAULTTYPE 'g' /* default number format type for printf() */ |
| #define MAXPRECISION DBL_DIG /* maximum number precision for printf() */ |
| |
| #define HOME_UNITS_ENV "MYUNITSFILE" /* Personal units file environment var */ |
| |
| #define NOERROR_KEYWORD "noerror " /* The trailing space is important */ |
| #define CO_NOARG -1 |
| |
| #define HELPCOMMAND "help" /* Command to request help at prompt */ |
| #define SEARCHCOMMAND "search" /* Command to request text search of units */ |
| #define UNITMATCH "?" /* Command to request conformable units */ |
| char *exit_commands[]={"quit","exit",0}; |
| char *all_commands[]={"quit","exit",HELPCOMMAND,SEARCHCOMMAND,UNITMATCH,0}; |
| |
| /* Key words for function definitions */ |
| struct { |
| char *word; |
| char delimit; |
| int checkopen; /* allow open intervals with parentheses */ |
| } fnkeywords[]={ {"units=", FUNCSEPCHAR, 0}, |
| {"domain=", ',', 1}, |
| {"range=", ',',1}, |
| {NOERROR_KEYWORD, ' ',CO_NOARG}, |
| {0,0}}; |
| #define FN_UNITS 0 |
| #define FN_DOMAIN 1 |
| #define FN_RANGE 2 |
| #define FN_NOERROR 3 |
| |
| char *builtins[] = {"sin", "cos", "tan","ln", "log", "exp", |
| "acos", "atan", "asin", "sqrt", "cuberoot", "per", |
| "sinh", "cosh", "tanh", "asinh", "atanh", "acosh", 0}; |
| |
| struct { |
| char *format; /* printf() format specification for numeric output */ |
| int width; /* printf() width from format */ |
| int precision; /* printf() precision from format */ |
| char type; /* printf() type from format */ |
| } num_format; |
| |
| |
| struct { /* Program command line option flags */ |
| int |
| interactive, |
| unitlists, /* Perform unit list output if set */ |
| oneline, /* Suppresses the second line of output */ |
| quiet, /* Supress prompting (-q option) */ |
| round, /* Round the last of unit list output to nearest integer */ |
| showconformable, /* */ |
| showfactor, /* */ |
| strictconvert, /* Strict conversion (disables reciprocals) */ |
| unitcheck, /* Enable unit checking: 1 for regular check, 2 for verbose*/ |
| verbose, /* Flag for output verbosity */ |
| readline; /* Using readline library? */ |
| } flags; |
| |
| |
| #define UTF8MARKER "\xEF\xBB\xBF" /* UTF-8 marker inserted by some Windows */ |
| /* programs at start of a UTF-8 file */ |
| |
| struct parseflag parserflags; /* parser options */ |
| |
| |
| char *homeunitsfile = ".units"; /* Units filename in home directory */ |
| char *homedir = NULL; /* User's home direcotry */ |
| char *homedir_error = NULL; /* Error message for home directory search */ |
| char *pager; /* Actual pager (from PAGER environment var) */ |
| char *mylocale; /* Locale in effect (from LC_CTYPE or LANG) */ |
| int utf8mode; /* Activate UTF8 support */ |
| char *powerstring = "^"; /* Exponent character used in output */ |
| char *unitsfiles[MAXFILES+1]; /* Null terminated list of units file names */ |
| char *logfilename=NULL; /* Filename for logging */ |
| FILE *logfile=NULL; /* File for logging */ |
| char *promptprefix=NULL; /* Prefix added to prompt */ |
| char *progname; /* Used in error messages */ |
| char *fullprogname; /* Full path of program; printversion() uses */ |
| char *progdir; /* Used to find supporting files */ |
| char *datadir; /* Used to find supporting files */ |
| char *deftext="";/* Output text when printing definition */ |
| char *digits = "0123456789.,"; |
| |
| |
| #define QUERYHAVE "You have: " /* Prompt text for units to convert from */ |
| #define QUERYWANT "You want: " /* Prompt text for units to convert to */ |
| |
| #define LOGFROM "From: " /* tag for log file */ |
| #define LOGTO "To: " /* tag for log file */ |
| |
| |
| #define HASHSIZE 101 /* Values from K&R 2nd ed., Sect. 6.6 */ |
| #define HASHNUMBER 31 |
| |
| #define SIMPLEHASHSIZE 128 |
| #define simplehash(str) (*(str) & 127) /* "hash" value for prefixes */ |
| |
| #define POINTER "^" /* pointer to location of a parse error */ |
| |
| #define ERRNUMFMT "%.8g" /* Numerical format for certain error messages */ |
| |
| char *errormsg[]={ |
| /* 0 */ "Successful completion", |
| /* 1 */ "Parse error", |
| /* 2 */ "Product overflow", |
| /* 3 */ "Unit reduction error (bad unit definition)", |
| /* 4 */ "Circular unit definition", |
| /* 5 */ "Invalid sum or difference of non-conformable units", |
| /* 6 */ "Unit not dimensionless", |
| /* 7 */ "Unit not a root", |
| /* 8 */ "Unknown unit", |
| /* 9 */ "Bad argument", |
| /* 10 */ "Weird nonlinear unit type (bug in program)", |
| /* 11 */ "Function argument has wrong dimension", |
| /* 12 */ "Argument of function outside domain", |
| /* 13 */ "Nonlinear unit definition has unit error", |
| /* 14 */ "No inverse defined", |
| /* 15 */ "Parser memory overflow (recursive function definition?)", |
| /* 16 */ "Argument wrong dimension or bad nonlinear unit definition", |
| /* 17 */ "Cannot open units file", |
| /* 18 */ "Units file contains errors", |
| /* 19 */ "Memory allocation error", |
| /* 20 */ "Malformed number", |
| /* 21 */ "Unit name ends with a digit other than 0 or 1 without preceding '_'", |
| /* 22 */ "No previous result; '_' not set", |
| /* 23 */ "Base unit not dimensionless; rational exponent required", |
| /* 24 */ "Base unit not a root", |
| /* 25 */ "Exponent not dimensionless", |
| /* 26 */ "Unknown function name", |
| /* 27 */ "Overflow: number too large", |
| /* 28 */ "Underflow: number too small" |
| }; |
| |
| char *invalid_utf8 = "invalid/nonprinting UTF-8"; |
| |
| char *irreducible=0; /* Name of last irreducible unit */ |
| |
| |
| /* Hash table for unit definitions. */ |
| |
| struct unitlist { |
| char *name; /* unit name */ |
| char *value; /* unit value */ |
| int linenumber; /* line in units data file where defined */ |
| char *file; /* file where defined */ |
| struct unitlist *next; /* next item in list */ |
| } *utab[HASHSIZE]; |
| |
| |
| /* Table for prefix definitions. */ |
| |
| struct prefixlist { |
| int len; /* length of name string */ |
| char *name; /* prefix name */ |
| char *value; /* prefix value */ |
| int linenumber; /* line in units data file where defined */ |
| char *file; /* file where defined */ |
| struct prefixlist *next; /* next item in list */ |
| } *ptab[SIMPLEHASHSIZE]; |
| |
| |
| struct wantalias { |
| char *name; |
| char *definition; |
| struct wantalias *next; |
| int linenumber; |
| char *file; |
| }; |
| |
| struct wantalias *firstalias = 0; |
| struct wantalias **aliaslistend = &firstalias; /* Next list entry goes here */ |
| |
| /* Table for function definitions */ |
| |
| struct func *ftab[SIMPLEHASHSIZE]; |
| |
| /* |
| Used for passing parameters to the parser when we are in the process |
| of parsing a unit function. If function_parameter is non-nil, then |
| whenever the text in function_parameter appears in a unit expression |
| it is replaced by the unit value stored in parameter_value. |
| */ |
| |
| char *function_parameter = 0; |
| struct unittype *parameter_value = 0; |
| |
| /* Stores the last result value for replacement with '_' */ |
| |
| int lastunitset = 0; |
| struct unittype lastunit; |
| |
| char *NULLUNIT = ""; /* Used for units that are canceled during reduction */ |
| |
| #define startswith(string, prefix) (!strncmp(string, prefix, strlen(prefix))) |
| #define lastchar(string) (*((string)+strlen(string)-1)) |
| #define emptystr(string) (*(string)==0) |
| #define nonempty(list) ((list) && *(list)) |
| |
| |
| #ifdef READLINE |
| |
| char *historyfile; /* Filename for readline history */ |
| int init_history_length; /* Length of history read from the history file*/ |
| int init_history_base; |
| |
| void |
| save_history(void) |
| { |
| int newentries; |
| int err; |
| |
| newentries = history_length-init_history_length; |
| if (history_max_entries > 0){ |
| newentries += history_base - init_history_base; |
| if (newentries > history_max_entries) |
| newentries = history_max_entries; |
| } |
| |
| err = append_history(newentries,historyfile); |
| if (err){ |
| if (err == ENOENT) |
| err = write_history(historyfile); |
| if (err) { |
| printf("Unable to write history to '%s': %s\n",historyfile,strerror(err)); |
| return; |
| } |
| } |
| history_truncate_file(historyfile,MAXHISTORYFILE); |
| } |
| #endif |
| |
| |
| /* Increases the buffer by BUFGROW bytes and leaves the new pointer in buf |
| and the new buffer size in bufsize. */ |
| |
| #define BUFGROW 100 |
| |
| void |
| growbuffer(char **buf, int *bufsize) |
| { |
| int usemalloc; |
| |
| usemalloc = !*buf || !*bufsize; |
| *bufsize += BUFGROW; |
| if (usemalloc) |
| *buf = malloc(*bufsize); |
| else |
| *buf = realloc(*buf,*bufsize); |
| if (!*buf){ |
| fprintf(stderr, "%s: memory allocation error (growbuffer)\n",progname); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| |
| FILE * |
| openfile(char *file,char *mode) |
| { |
| FILE *fileptr; |
| |
| struct stat statbuf; |
| if (stat(file, &statbuf)==0 && statbuf.st_mode & S_IFDIR){ |
| errno=EISDIR; |
| return NULL; |
| } |
| fileptr = fopen(file,mode); |
| return fileptr; |
| } |
| |
| |
| char log_buffer[4096] = {0}; |
| char *log_buffer_ptr = log_buffer; |
| |
| |
| void |
| logprintf(const char *format, ...) |
| { |
| va_list args; |
| |
| va_start(args, format); |
| vprintf(format, args); |
| va_end(args); |
| |
| va_start(args, format); |
| log_buffer_ptr += vsnprintf(log_buffer_ptr, log_buffer + 4096 - log_buffer_ptr, format, args); |
| va_end(args); |
| |
| if (logfile) { |
| va_start(args, format); |
| vfprintf(logfile, format, args); |
| va_end(args); |
| } |
| } |
| |
| void |
| logputchar(char c) |
| { |
| putchar(c); |
| *(log_buffer_ptr++) = c; |
| if (logfile) fputc(c, logfile); |
| } |
| |
| void |
| logputs(const char *s) |
| { |
| fputs(s, stdout); |
| log_buffer_ptr += strlcpy(log_buffer_ptr, s, log_buffer + 4096 - log_buffer_ptr); |
| if (logfile) fputs(s, logfile); |
| } |
| |
| |
| /* Look for a subscript in the input string. A subscript starts with |
| '_' and is followed by a sequence of only digits (matching the |
| regexp "_[0-9]+"). The function returns 1 if it finds a subscript |
| and zero otherwise. Note that it returns 1 for an input that is |
| entirely subscript, with the '_' appearing in the first position. */ |
| |
| int |
| hassubscript(const char *str) |
| { |
| const char *ptr = &lastchar(str); |
| while (ptr>str){ |
| if (!strchr(digits, *ptr)) |
| return 0; |
| ptr--; |
| if (*ptr=='_') |
| return 1; |
| } |
| return 0; |
| } |
| |
| |
| /* replace various Unicode operator symbols with stanard ASCII equivalents */ |
| void |
| replace_operators(char *input) |
| { |
| struct{ |
| char *unicode; |
| char replacement; |
| } repl_table[]={ |
| {"\xE2\x80\x92", '-'}, /* U+2012: figure dash */ |
| {"\xE2\x80\x93", '-'}, /* U+2013: en dash */ |
| {"\xE2\x88\x92", '-'}, /* U+2212: minus */ |
| {"\xC3\x97", '*'}, /* U+00D7: times */ |
| {"\xE2\xA8\x89" ,'*'}, /* U+2A09: N-ary times operator */ |
| {"\xC2\xB7", '*'}, /* U+00B7: middle dot */ |
| {"\xE2\x8B\x85", '*'}, /* U+22C5: dot operator */ |
| {"\xC3\xB7", '/'}, /* U+00F7: division sign */ |
| {"\xE2\x88\x95", '/'}, /* U+2215: division slash */ |
| {"\xE2\x81\x84", '|'}, /* U+2044: fraction slash */ |
| {0,0} |
| }; |
| char *inptr, *outptr, *ptr; |
| |
| for (int i=0; repl_table[i].unicode; i++) { |
| inptr = outptr = input; |
| do { |
| ptr = strstr(inptr, repl_table[i].unicode); /* find next unicode symbol */ |
| if (ptr) { |
| while (inptr < ptr) /* copy the input up to the unicode */ |
| *outptr++ = *inptr++; |
| inptr = ptr + strlen(repl_table[i].unicode); /* skip over unicode */ |
| *outptr++ = repl_table[i].replacement; /* Output replacement */ |
| } |
| } while (ptr); |
| |
| /* If replacement were made, copy remaining input to end of string */ |
| |
| if (inptr > input) { |
| while (*inptr) |
| *outptr++ = *inptr++; |
| *outptr = '\0'; |
| } |
| } |
| } |
| |
| |
| |
| /* Replace all control chars with a space */ |
| |
| void |
| replacectrlchars(char *string) |
| { |
| for(;*string;string++) |
| if (iscntrl(*string)) |
| *string = ' '; |
| } |
| |
| /* |
| Fetch a line of data with backslash for continuation. The |
| parameter count is incremented to report the number of newlines |
| that are read so that line numbers can be accurately reported. |
| */ |
| |
| char * |
| fgetscont(char *buf, int size, FILE *file, int *count) |
| { |
| if (!fgets(buf,size,file)) |
| return 0; |
| (*count)++; |
| while(strlen(buf)>=2 && 0==strcmp(buf+strlen(buf)-2,"\\\n")){ |
| (*count)++; |
| buf[strlen(buf)-2] = 0; /* delete trailing \n and \ char */ |
| if (strlen(buf)>=size-1) /* return if the buffer is full */ |
| return buf; |
| if (!fgets(buf+strlen(buf), size - strlen(buf), file)) |
| return buf; /* already read some data so return success */ |
| } |
| if (lastchar(buf) == '\\') { /* If last char of buffer is \ then */ |
| ungetc('\\', file); /* we don't know if it is followed by */ |
| lastchar(buf) = 0; /* a \n, so put it back and try again */ |
| } |
| return buf; |
| } |
| |
| |
| /* |
| Gets arbitrarily long input data into a buffer using growbuffer(). |
| Returns 0 if no data is read. Increments count by the number of |
| newlines read unless it points to NULL. |
| |
| Replaces tabs and newlines with spaces before returning the result. |
| */ |
| |
| char * |
| fgetslong(char **buf, int *bufsize, FILE *file, int *count) |
| { |
| int dummy; |
| if (!count) |
| count = &dummy; |
| if (!*bufsize) growbuffer(buf,bufsize); |
| if (!fgetscont(*buf, *bufsize, file, count)) |
| return 0; |
| while (lastchar(*buf) != '\n' && !feof(file)){ |
| growbuffer(buf, bufsize); |
| fgetscont(*buf+strlen(*buf), *bufsize-strlen(*buf), file, count); |
| (*count)--; |
| } |
| /* These nonprinting characters must be removed so that the test |
| for UTF-8 validity will work. */ |
| replacectrlchars(*buf); |
| return *buf; |
| } |
| |
| /* Allocates memory and aborts if malloc fails. */ |
| |
| void * |
| mymalloc(int bytes,const char *mesg) |
| { |
| void *pointer; |
| |
| pointer = malloc(bytes); |
| if (!pointer){ |
| fprintf(stderr, "%s: memory allocation error %s\n", progname, mesg); |
| exit(EXIT_FAILURE); |
| } |
| return pointer; |
| } |
| |
| |
| /* Duplicates a string */ |
| |
| char * |
| dupstr(const char *str) |
| { |
| char *ret; |
| |
| ret = mymalloc(strlen(str) + 1,"(dupstr)"); |
| strcpy(ret, str); |
| return ret; |
| } |
| |
| /* Duplicates a string that is not null-terminated, |
| adding the null to the copy */ |
| |
| char * |
| dupnstr(const char *string, int length) |
| { |
| char *newstr; |
| newstr = mymalloc(length+1,"(dupnstr)"); |
| strncpy(newstr, string, length); |
| newstr[length]=0; |
| return newstr; |
| } |
| |
| |
| #ifdef SUPPORT_UTF8 |
| |
| /* |
| The strwidth function gives the printed width of a UTF-8 byte sequence. |
| It will return -1 if the sequence is an invalid UTF-8 sequence or |
| if the sequence contains "nonprinting" characters. Note that \n and \t are |
| "nonprinting" characters. |
| */ |
| |
| int |
| strwidth(const char *str) |
| { |
| wchar_t *widestr; |
| int len; |
| |
| if (!utf8mode) |
| return strlen(str); |
| len = strlen(str)+1; |
| widestr = mymalloc(sizeof(wchar_t)*len, "(strwidth)"); |
| len = mbsrtowcs(widestr, &str, len, NULL); |
| |
| if (len==-1){ |
| free(widestr); |
| return -1; /* invalid multibyte sequence */ |
| } |
| |
| len=wcswidth(widestr, len); |
| free(widestr); |
| return len; |
| } |
| #else |
| # define strwidth strlen |
| #endif |
| |
| |
| |
| /* hashing algorithm for units */ |
| |
| unsigned |
| uhash(const char *str) |
| { |
| unsigned hashval; |
| |
| for (hashval = 0; *str; str++) |
| hashval = *str + HASHNUMBER * hashval; |
| return (hashval % HASHSIZE); |
| } |
| |
| |
| /* Lookup a unit in the units table. Returns the definition, or NULL |
| if the unit isn't found in the table. */ |
| |
| struct unitlist * |
| ulookup(const char *str) |
| { |
| struct unitlist *uptr; |
| |
| for (uptr = utab[uhash(str)]; uptr; uptr = uptr->next) |
| if (strcmp(str, uptr->name) == 0) |
| return uptr; |
| return NULL; |
| } |
| |
| /* Lookup a prefix in the prefix table. Finds the longest prefix that |
| matches the beginning of the input string. Returns NULL if no |
| prefixes match. */ |
| |
| struct prefixlist * |
| plookup(const char *str) |
| { |
| struct prefixlist *prefix; |
| struct prefixlist *bestprefix=NULL; |
| int bestlength=0; |
| |
| for (prefix = ptab[simplehash(str)]; prefix; prefix = prefix->next) { |
| if (prefix->len > bestlength && !strncmp(str, prefix->name, prefix->len)){ |
| bestlength = prefix->len; |
| bestprefix = prefix; |
| } |
| } |
| return bestprefix; |
| } |
| |
| /* Look up function in the function linked list */ |
| |
| struct func * |
| fnlookup(const char *str) |
| { |
| struct func *funcptr; |
| |
| for(funcptr=ftab[simplehash(str)];funcptr;funcptr = funcptr->next) |
| if (!strcmp(funcptr->name, str)) |
| return funcptr; |
| return 0; |
| } |
| |
| struct wantalias * |
| aliaslookup(const char *str) |
| { |
| struct wantalias *aliasptr; |
| for(aliasptr = firstalias; aliasptr; aliasptr=aliasptr->next) |
| if (!strcmp(aliasptr->name, str)) |
| return aliasptr; |
| return 0; |
| } |
| |
| |
| /* Insert a new function into the linked list of functions */ |
| |
| void |
| addfunction(struct func *newfunc) |
| { |
| int val; |
| |
| val = simplehash(newfunc->name); |
| newfunc->next = ftab[val]; |
| ftab[val] = newfunc; |
| } |
| |
| |
| /* Free the fields in the function except for the name so that it |
| can be redefined. It remains in position in the linked list. */ |
| void |
| freefunction(struct func *funcentry) |
| { |
| if (funcentry->table){ |
| free(funcentry->table); |
| free(funcentry->tableunit); |
| } else { |
| free(funcentry->forward.param); |
| free(funcentry->forward.def); |
| if (funcentry->forward.domain_min) free(funcentry->forward.domain_min); |
| if (funcentry->forward.domain_max) free(funcentry->forward.domain_max); |
| if (funcentry->inverse.domain_min) free(funcentry->inverse.domain_min); |
| if (funcentry->inverse.domain_max) free(funcentry->inverse.domain_max); |
| if (funcentry->forward.dimen) free(funcentry->forward.dimen); |
| if (funcentry->inverse.dimen) free(funcentry->inverse.dimen); |
| if (funcentry->inverse.def) free(funcentry->inverse.def); |
| if (funcentry->inverse.param) free(funcentry->inverse.param); |
| } |
| } |
| |
| /* Remove leading and trailing spaces from the input */ |
| |
| void |
| removespaces(char *in) |
| { |
| char *ptr; |
| if (*in) { |
| for(ptr = in + strlen(in) - 1; *ptr==' '; ptr--); /* Last non-space */ |
| *(ptr+1)=0; |
| if (*in==' '){ |
| ptr = in + strspn(in," "); |
| memmove(in, ptr, strlen(ptr)+1); |
| } |
| } |
| } |
| |
| |
| /* |
| Looks up an inverse function given as a ~ character followed by |
| spaces and then the function name. The spaces will be deleted as a |
| side effect. If an inverse function is found returns the function |
| pointer, otherwise returns null. |
| */ |
| |
| struct func * |
| invfnlookup(char *str) |
| { |
| if (*str != '~') |
| return 0; |
| removespaces(str+1); |
| return fnlookup(str+1); |
| } |
| |
| |
| char * |
| strip_comment(char *line) |
| { |
| char *comment = 0; |
| |
| if ((line = strchr(line,COMMENTCHAR))) { |
| comment = line+1; |
| *line = 0; |
| } |
| return comment; |
| } |
| |
| |
| /* Print string but replace two consecutive spaces with one space. */ |
| |
| void |
| tightprint(FILE *outfile, char *string) |
| { |
| while(*string){ |
| fputc(*string, outfile); |
| if (*string != ' ') string++; |
| else while(*string==' ') string++; |
| } |
| } |
| |
| /* |
| Copy string to buf, replacing two or more consecutive spaces with |
| one space. |
| */ |
| void |
| tightbufprint(char *buf, char *string) |
| { |
| while(*string) { |
| *buf++ = *string; |
| if (*string != ' ') |
| string++; |
| else { |
| while(*string==' ') |
| string++; |
| } |
| } |
| *buf = '\0'; |
| } |
| |
| |
| #define readerror (goterr=1) && errfile && fprintf |
| |
| #define VAGUE_ERR "%s: error in units file '%s' line %d\n", \ |
| progname, file, linenum |
| |
| /* Print out error message encountered while reading the units file. */ |
| |
| |
| /* |
| Splits the line into two parts. The first part is space delimited. |
| The second part is everything else. Removes trailing spaces from |
| the second part. Returned items are null if no parameter was found. |
| */ |
| |
| void |
| splitline(char *line, char **first, char **second) |
| { |
| *second = 0; |
| *first = strtok(line, " "); |
| if (*first){ |
| *second = strtok(0, "\n"); |
| if (*second){ |
| removespaces(*second); |
| if (emptystr(*second)) |
| *second = 0; |
| } |
| } |
| } |
| |
| |
| /* see if character is part of a valid decimal number */ |
| |
| int |
| isdecimal(char c) |
| { |
| return strchr(digits, c) != NULL; |
| } |
| |
| |
| /* |
| Check for some invalid unit names. Print error message. Returns 1 if |
| unit name is bad, zero otherwise. |
| */ |
| int |
| checkunitname(char *name, int linenum, char *file, FILE *errfile) |
| { |
| char nonunitchars[] = "~;+-*/|^)"; /* Also defined in parse.y with a few more characters */ |
| char **ptr; |
| char *cptr; |
| |
| if ((cptr=strpbrk(name, nonunitchars))){ |
| if (errfile) fprintf(errfile, |
| "%s: unit '%s' in units file '%s' on line %d ignored. It contains invalid character '%c'\n", |
| progname, name, file, linenum, *cptr); |
| return 1; |
| } |
| if (strchr(digits, name[0])){ |
| if (errfile) fprintf(errfile, |
| "%s: unit '%s' in units file '%s' on line %d ignored. It starts with a digit\n", |
| progname, name, file, linenum); |
| return 1; |
| } |
| for(ptr=builtins;*ptr;ptr++) |
| if (!strcmp(name, *ptr)){ |
| if (errfile) fprintf(errfile, |
| "%s: redefinition of built-in function '%s' in file '%s' on line %d ignored.\n", |
| progname, name, file, linenum); |
| return 1; |
| } |
| for(ptr=all_commands;*ptr;ptr++) |
| if (!strcmp(name, *ptr)){ |
| if (errfile) fprintf(errfile, |
| "%s: unit name '%s' in file '%s' on line %d may be hidden by command with the same name.\n", |
| progname, name, file, linenum); |
| } |
| return 0; |
| } |
| |
| int |
| newunit(char *unitname, char *unitdef, int *count, int linenum, |
| char *file,FILE *errfile, int redefine, int userunit) |
| { |
| struct unitlist *uptr; |
| unsigned hashval; |
| |
| /* units ending with '_' create ambiguity for exponents */ |
| |
| if ((unitname[0]=='_' && !userunit) || lastchar(unitname)=='_'){ |
| if (errfile) fprintf(errfile, |
| "%s: unit '%s' on line %d of '%s' ignored. It starts or ends with '_'\n", |
| progname, unitname, linenum, file); |
| return E_BADFILE; |
| } |
| |
| /* Units that end in [2-9] can never be accessed */ |
| if (strchr(".,23456789", lastchar(unitname)) && !hassubscript(unitname)){ |
| if (errfile) fprintf(errfile, |
| "%s: unit '%s' on line %d of '%s' ignored. %s\n", |
| progname, unitname, linenum, file,errormsg[E_UNITEND]); |
| return E_BADFILE; |
| } |
| |
| if (checkunitname(unitname, linenum, file, errfile)) |
| return E_BADFILE; |
| |
| if ((uptr=ulookup(unitname))) { /* Is it a redefinition? */ |
| if (flags.unitcheck && errfile && !redefine) |
| fprintf(errfile, |
| "%s: unit '%s' defined on line %d of '%s' is redefined on line %d of '%s'.\n", |
| progname, unitname, uptr->linenumber,uptr->file, |
| linenum, file); |
| free(uptr->value); |
| } else { |
| /* make new units table entry */ |
| |
| uptr = (struct unitlist *) mymalloc(sizeof(*uptr),"(newunit)"); |
| uptr->name = dupstr(unitname); |
| |
| /* install unit name/value pair in list */ |
| |
| hashval = uhash(uptr->name); |
| uptr->next = utab[hashval]; |
| utab[hashval] = uptr; |
| (*count)++; |
| } |
| uptr->value = dupstr(unitdef); |
| uptr->linenumber = linenum; |
| uptr->file = file; |
| return 0; |
| } |
| |
| |
| |
| int |
| newprefix(char *unitname, char *unitdef, int *count, int linenum, |
| char *file,FILE *errfile, int redefine) |
| { |
| struct prefixlist *pfxptr; |
| unsigned pval; |
| |
| lastchar(unitname) = 0; |
| if (checkunitname(unitname,linenum,file,errfile)) |
| return E_BADFILE; |
| if ((pfxptr = plookup(unitname)) /* already there: redefinition */ |
| && !strcmp(pfxptr->name, unitname)){ |
| if (flags.unitcheck && errfile && !redefine) |
| fprintf(errfile, |
| "%s: prefix '%s-' defined on line %d of '%s' is redefined on line %d of '%s'.\n", |
| progname, unitname, pfxptr->linenumber,pfxptr->file, |
| linenum, file); |
| free(pfxptr->value); |
| } else { |
| pfxptr = (struct prefixlist *) mymalloc(sizeof(*pfxptr),"(newprefix)"); |
| pfxptr->name = dupstr(unitname); |
| pfxptr->len = strlen(unitname); |
| pval = simplehash(unitname); |
| pfxptr->next = ptab[pval]; |
| ptab[pval] = pfxptr; |
| (*count)++; |
| } |
| pfxptr->value = dupstr(unitdef); |
| pfxptr->linenumber = linenum; |
| pfxptr->file = file; |
| return 0; |
| } |
| |
| |
| /* |
| parsepair() looks for data of the form [text1,text2] where the ',' is a |
| specified delimiter. The second argument, text2, is optional and if it's |
| missing then second is set to NULL. The parameters are allowed to be |
| empty strings. The function returns the first character after the |
| closing bracket if no errors occur or the NULL pointer on error. |
| */ |
| |
| char * |
| parsepair(char *input, char **first, char **second, |
| int *firstopen, int *secondopen, char delimiter, int checkopen, |
| char *unitname, int linenum, char *file,FILE *errfile) |
| { |
| char *start, *end, *middle; |
| |
| start = strpbrk(input, checkopen?"[(":"["); |
| if (!start){ |
| if (errfile) fprintf(errfile, |
| "%s: expecting '[' %s in definition of '%s' in '%s' line %d\n", |
| progname, checkopen ? "or '('":"", unitname, file, linenum); |
| return 0; |
| } |
| if (*start=='(') *firstopen=1; |
| else *firstopen=0; |
| *start++=0; |
| removespaces(input); |
| if (!emptystr(input)){ |
| if (errfile) fprintf(errfile, |
| "%s: unexpected characters before '%c' in definition of '%s' in '%s' line %d\n", |
| progname, *firstopen?'(':'[',unitname, file, linenum); |
| return 0; |
| } |
| end = strpbrk(start, checkopen?"])":"]"); |
| if (!end){ |
| if (errfile) fprintf(errfile, |
| "%s: expecting ']' %s in definition of '%s' in '%s' line %d\n", |
| progname, checkopen?"or ')'":"",unitname, file, linenum); |
| return 0; |
| } |
| if (*end==')') *secondopen=1; |
| else *secondopen=0; |
| *end++=0; |
| |
| middle = strchr(start,delimiter); |
| |
| if (middle){ |
| *middle++=0; |
| removespaces(middle); |
| *second = middle; |
| } else |
| *second = 0; |
| |
| removespaces(start); |
| *first = start; |
| return end; |
| } |
| |
| |
| /* |
| Extract numbers from two text strings and place them into pointers. |
| Has two error codes for decreasing interval or bad numbers in the |
| text strings. Returns 0 on success. |
| */ |
| |
| #define EI_ERR_DEC 1 /* Decreasing interval */ |
| #define EI_ERR_MALF 2 /* Malformed number */ |
| |
| int |
| extract_interval(char *first, char *second, |
| double **firstout, double **secondout) |
| { |
| double val; |
| char *end; |
| |
| if (!emptystr(first)){ |
| val = strtod(first, &end); |
| if (*end) |
| return EI_ERR_MALF; |
| else { |
| *firstout=(double *)mymalloc(sizeof(double), "(extract_interval)"); |
| **firstout = val; |
| } |
| } |
| if (nonempty(second)) { |
| val = strtod(second, &end); |
| if (*end) |
| return EI_ERR_MALF; |
| else if (*firstout && **firstout>=val) |
| return EI_ERR_DEC; |
| else { |
| *secondout=(double *)mymalloc(sizeof(double), "(extract_interval)"); |
| **secondout = val; |
| } |
| } |
| return 0; |
| } |
| |
| |
| |
| void |
| copyfunctype(struct functype *dest, struct functype *src) |
| { |
| dest->domain_min_open = src->domain_min_open; |
| dest->domain_max_open = src->domain_max_open; |
| dest->param = dest->def = dest->dimen = NULL; |
| dest->domain_min = dest->domain_max = NULL; |
| if (src->param) dest->param = dupstr(src->param); |
| if (src->def) dest->def = dupstr(src->def); |
| if (src->dimen) dest->dimen = dupstr(src->dimen); |
| if (src->domain_min){ |
| dest->domain_min = (double *) mymalloc(sizeof(double), "(copyfunctype)"); |
| *dest->domain_min = *src->domain_min; |
| } |
| if (src->domain_max){ |
| dest->domain_max = (double *) mymalloc(sizeof(double), "(copyfunctype)"); |
| *dest->domain_max = *src->domain_max; |
| } |
| } |
| |
| |
| int |
| copyfunction(char *unitname, char *funcname, int *count, int linenum, |
| char *file, FILE *errfile) |
| { |
| struct func *source, *funcentry; |
| int i; |
| if (checkunitname(unitname, linenum, file, errfile)) |
| return E_BADFILE; |
| removespaces(funcname); |
| i = strlen(funcname)-2; /* strip trailing () if present */ |
| if (i>0 && !strcmp(funcname+i,"()")) |
| funcname[i]=0; |
| source = fnlookup(funcname); |
| if (!source) { |
| if (errfile){ |
| if (!strpbrk(funcname," ;][()+*/-^")) |
| fprintf(errfile,"%s: bad definition for '%s' in '%s' line %d, function '%s' not defined\n", |
| progname, unitname, file, linenum, funcname); |
| else |
| fprintf(errfile,"%s: bad function definition of '%s' in '%s' line %d\n", |
| progname,unitname,file,linenum); |
| } |
| return E_BADFILE; |
| } |
| if ((funcentry=fnlookup(unitname))){ |
| if (flags.unitcheck && errfile) |
| fprintf(errfile, |
| "%s: function '%s' defined on line %d of '%s' is redefined on line %d of '%s'.\n", |
| progname, unitname, funcentry->linenumber,funcentry->file, |
| linenum, file); |
| freefunction(funcentry); |
| } else { |
| funcentry = (struct func*)mymalloc(sizeof(struct func),"(newfunction)"); |
| funcentry->name = dupstr(unitname); |
| addfunction(funcentry); |
| (*count)++; |
| } |
| funcentry->linenumber = linenum; |
| funcentry->file = file; |
| funcentry->skip_error_check = source->skip_error_check; |
| if (source->table){ |
| funcentry->tablelen = source->tablelen; |
| funcentry->tableunit = dupstr(source->tableunit); |
| funcentry->table = (struct pair *) |
| mymalloc(sizeof(struct pair)*funcentry->tablelen, "(copyfunction)"); |
| for(i=0;i<funcentry->tablelen;i++){ |
| funcentry->table[i].location = source->table[i].location; |
| funcentry->table[i].value = source->table[i].value; |
| } |
| } else { |
| funcentry->table = 0; |
| copyfunctype(&funcentry->forward, &source->forward); |
| copyfunctype(&funcentry->inverse, &source->inverse); |
| } |
| return 0; |
| } |
| |
| |
| #define FREE_STUFF {if (forward_dim) free(forward_dim);\ |
| if (inverse_dim) free(inverse_dim);\ |
| if (domain_min) free(domain_min);\ |
| if (domain_max) free(domain_max);\ |
| if (range_min) free(range_min);\ |
| if (range_max) free(range_max);} |
| |
| #define REPEAT_ERR \ |
| if (errfile) fprintf(errfile, \ |
| "%s: keyword '%s' repeated in definition of '%s' on line %d of '%s'.\n",\ |
| progname,fnkeywords[i].word,unitname, linenum, file) |
| |
| int |
| newfunction(char *unitname, char *unitdef, int *count, |
| int linenum, char *file,FILE *errfile, int redefine) |
| { |
| char *start, *end, *inv, *forward_dim, *inverse_dim, *first, *second; |
| double *domain_min, *domain_max, *range_min, *range_max; |
| struct func *funcentry; |
| int looking_for_keywords,i, firstopen, secondopen; |
| int domain_min_open, domain_max_open, range_min_open, range_max_open; |
| int noerror = 0; |
| |
| if (*unitname=='('){ |
| if (errfile) fprintf(errfile, |
| "%s: unit '%s' on line %d of '%s' ignored. It starts with a '('\n", |
| progname, unitname, linenum, file); |
| return E_BADFILE; |
| } |
| /* coverity[returned_null] */ |
| start = strchr(unitname,'('); |
| end = strchr(unitname,')'); |
| *start++ = 0; |
| |
| if (checkunitname(unitname,linenum,file,errfile)) |
| return E_BADFILE; |
| |
| if (start==end) /* no argument: function() so make a function copy */ |
| return copyfunction(unitname, unitdef, count, linenum, file, errfile); |
| |
| if (!end || strlen(end)>1){ |
| if (errfile) fprintf(errfile, |
| "%s: bad function definition of '%s' in '%s' line %d\n", |
| progname,unitname,file,linenum); |
| return E_BADFILE; |
| } |
| *end=0; |
| forward_dim = NULL; |
| inverse_dim = NULL; |
| domain_min = NULL; |
| domain_max = NULL; |
| range_min = NULL; |
| range_max = NULL; |
| domain_min_open = 0; |
| domain_max_open = 0; |
| range_min_open = 0; |
| range_max_open = 0; |
| looking_for_keywords=1; |
| while (looking_for_keywords) { |
| looking_for_keywords = 0; |
| for(i=0;fnkeywords[i].word;i++){ |
| if (startswith(unitdef, fnkeywords[i].word)){ |
| looking_for_keywords = 1; /* found keyword so keep looking */ |
| unitdef+=strlen(fnkeywords[i].word); |
| if (fnkeywords[i].checkopen!=CO_NOARG){ |
| unitdef = parsepair(unitdef,&first, &second, &firstopen, &secondopen, |
| fnkeywords[i].delimit, fnkeywords[i].checkopen, |
| unitname, linenum, file,errfile); |
| if (!unitdef){ |
| FREE_STUFF; |
| return E_BADFILE; |
| } |
| removespaces(unitdef); |
| } |
| if (i==FN_NOERROR) |
| noerror = 1; |
| if (i==FN_UNITS){ |
| if (forward_dim || inverse_dim){ |
| REPEAT_ERR; |
| return E_BADFILE; |
| } |
| forward_dim = dupstr(first); |
| if (second) |
| inverse_dim = dupstr(second); |
| } |
| if (i==FN_DOMAIN){ |
| int err=0; |
| if (domain_min || domain_max){ |
| REPEAT_ERR; |
| return E_BADFILE; |
| } |
| err = extract_interval(first,second,&domain_min, &domain_max); |
| domain_min_open = firstopen; |
| domain_max_open = secondopen; |
| if (err) |
| FREE_STUFF; |
| if (err==EI_ERR_DEC){ |
| if (errfile) fprintf(errfile, |
| "%s: second endpoint for domain must be greater than the first\n in definition of '%s' in '%s' line %d\n", |
| progname, unitname, file, linenum); |
| return E_BADFILE; |
| } |
| if (err==EI_ERR_MALF){ |
| if (errfile) fprintf(errfile, |
| "%s: malformed domain in definition of '%s' in '%s' line %d\n", |
| progname, unitname, file, linenum); |
| return E_BADFILE; |
| } |
| } |
| if (i==FN_RANGE){ |
| int err=0; |
| if (range_min || range_max){ |
| REPEAT_ERR; |
| FREE_STUFF; |
| return E_BADFILE; |
| } |
| err = extract_interval(first,second,&range_min, &range_max); |
| range_min_open = firstopen; |
| range_max_open = secondopen; |
| if (err) |
| FREE_STUFF; |
| if (err==EI_ERR_DEC){ |
| if (errfile) fprintf(errfile, |
| "%s: second endpoint for range must be greater than the first\n in definition of '%s' in '%s' line %d\n", |
| progname, unitname, file, linenum); |
| return E_BADFILE; |
| } |
| if (err==EI_ERR_MALF){ |
| if (errfile) fprintf(errfile, |
| "%s: malformed range in definition of '%s' in '%s' line %d\n", |
| progname, unitname, file, linenum); |
| return E_BADFILE; |
| } |
| } |
| } |
| } |
| } |
| |
| if (emptystr(unitdef)){ |
| if (errfile) fprintf(errfile, |
| "%s: function '%s' lacks a definition at line %d of '%s'\n", |
| progname, unitname, linenum, file); |
| FREE_STUFF; |
| return E_BADFILE; |
| } |
| |
| if (*unitdef=='['){ |
| if (errfile) fprintf(errfile, |
| "%s: function '%s' missing keyword before '[' on line %d of '%s'\n", |
| progname, unitname, linenum, file); |
| FREE_STUFF; |
| return E_BADFILE; |
| } |
| |
| /* Check that if domain and range are specified and nonzero then the |
| units are given. Otherwise these are meaningless. */ |
| if (!forward_dim && |
| ((domain_min && *domain_min) || (domain_max && *domain_max))){ |
| if (errfile) |
| fprintf(errfile,"%s: function '%s' defined on line %d of '%s' has domain with no units.\n", |
| progname, unitname, linenum, file); |
| FREE_STUFF; |
| return E_BADFILE; |
| } |
| if (!inverse_dim && |
| ((range_min && *range_min) || (range_max && *range_max))){ |
| if (errfile) |
| fprintf(errfile,"%s: function '%s' defined on line %d of '%s' has range with no units.\n", |
| progname, unitname, linenum, file); |
| FREE_STUFF; |
| return E_BADFILE; |
| } |
| if ((funcentry=fnlookup(unitname))){ |
| if (flags.unitcheck && errfile && !redefine) |
| fprintf(errfile, |
| "%s: function '%s' defined on line %d of '%s' is redefined on line %d of '%s'.\n", |
| progname, unitname, funcentry->linenumber,funcentry->file, |
| linenum, file); |
| freefunction(funcentry); |
| } else { |
| funcentry = (struct func*)mymalloc(sizeof(struct func),"(newfunction)"); |
| funcentry->name = dupstr(unitname); |
| addfunction(funcentry); |
| (*count)++; |
| } |
| funcentry->table = 0; |
| funcentry->skip_error_check = noerror; |
| funcentry->forward.dimen = forward_dim; |
| funcentry->inverse.dimen = inverse_dim; |
| funcentry->forward.domain_min = domain_min; |
| funcentry->forward.domain_max = domain_max; |
| funcentry->inverse.domain_min = range_min; |
| funcentry->inverse.domain_max = range_max; |
| funcentry->forward.domain_min_open = domain_min_open; |
| funcentry->forward.domain_max_open = domain_max_open; |
| funcentry->inverse.domain_min_open = range_min_open; |
| funcentry->inverse.domain_max_open = range_max_open; |
| inv = strchr(unitdef,FUNCSEPCHAR); |
| if (inv) |
| *inv++ = 0; |
| funcentry->forward.param = dupstr(start); |
| removespaces(unitdef); |
| funcentry->forward.def = dupstr(unitdef); |
| if (inv){ |
| removespaces(inv); |
| funcentry->inverse.def = dupstr(inv); |
| funcentry->inverse.param = dupstr(unitname); |
| } |
| else { |
| funcentry->inverse.def = 0; |
| funcentry->inverse.param = 0; |
| } |
| funcentry->linenumber = linenum; |
| funcentry->file = file; |
| return 0; |
| } |
| |
| |
| int |
| newtable(char *unitname,char *unitdef, int *count, |
| int linenum, char *file,FILE *errfile, int redefine) |
| { |
| char *start, *end; |
| char *tableunit; |
| int tablealloc, tabpt; |
| struct pair *tab; |
| struct func *funcentry; |
| int noerror = 0; |
| |
| /* coverity[returned_null] */ |
| tableunit = strchr(unitname,'['); |
| end = strchr(unitname,']'); |
| *tableunit++=0; |
| if (checkunitname(unitname, linenum, file, errfile)) |
| return E_BADFILE; |
| if (!end){ |
| if (errfile) fprintf(errfile,"%s: missing ']' in units file '%s' line %d\n", |
| progname,file,linenum); |
| return E_BADFILE; |
| } |
| if (strlen(end)>1){ |
| if (errfile) fprintf(errfile, |
| "%s: unexpected characters after ']' in units file '%s' line %d\n", |
| progname,file,linenum); |
| return E_BADFILE; |
| } |
| *end=0; |
| tab = (struct pair *)mymalloc(sizeof(struct pair)*20, "(newtable)"); |
| tablealloc=20; |
| tabpt = 0; |
| start = unitdef; |
| if (startswith(start, NOERROR_KEYWORD)) { |
| noerror = 1; |
| start += strlen(NOERROR_KEYWORD); |
| removespaces(start); |
| } |
| while (1) { |
| if (tabpt>=tablealloc){ |
| tablealloc+=20; |
| tab = (struct pair *)realloc(tab,sizeof(struct pair)*tablealloc); |
| if (!tab){ |
| if (errfile) fprintf(errfile, "%s: memory allocation error (newtable)\n", |
| progname); |
| return E_MEMORY; |
| } |
| } |
| tab[tabpt].location = strtod(start,&end); |
| if (start==end || (!emptystr(end) && *end !=' ')){ |
| if (!emptystr(start)) { |
| if (strlen(start)>15) start[15]=0; /* Truncate for error msg display */ |
| if (errfile) fprintf(errfile, |
| "%s: cannot parse table definition %s at '%s' on line %d of '%s'\n", |
| progname, unitname, start, linenum, file); |
| free(tab); |
| return E_BADFILE; |
| } |
| break; |
| } |
| if (tabpt>0 && tab[tabpt].location<=tab[tabpt-1].location){ |
| if (errfile) |
| fprintf(errfile,"%s: points don't increase (" ERRNUMFMT " to " ERRNUMFMT |
| ") in units file '%s' line %d\n", |
| progname, tab[tabpt-1].location, tab[tabpt].location, |
| file, linenum); |
| free(tab); |
| return E_BADFILE; |
| } |
| start=end+strspn(end," "); |
| tab[tabpt].value = strtod(start,&end); |
| if (start==end){ |
| if (errfile) |
| fprintf(errfile,"%s: missing value after " ERRNUMFMT " in units file '%s' line %d\n", |
| progname, tab[tabpt].location, file, linenum); |
| free(tab); |
| return E_BADFILE; |
| } |
| tabpt++; |
| start=end+strspn(end," ,"); |
| } |
| if ((funcentry=fnlookup(unitname))){ |
| if (flags.unitcheck && errfile && !redefine) |
| fprintf(errfile, |
| "%s: unit '%s' defined on line %d of '%s' is redefined on line %d of '%s'.\n", |
| progname, unitname, funcentry->linenumber,funcentry->file, |
| linenum, file); |
| freefunction(funcentry); |
| } else { |
| funcentry = (struct func *)mymalloc(sizeof(struct func),"(newtable)"); |
| funcentry->name = dupstr(unitname); |
| addfunction(funcentry); |
| (*count)++; |
| } |
| funcentry->tableunit = dupstr(tableunit); |
| funcentry->tablelen = tabpt; |
| funcentry->table = tab; |
| funcentry->skip_error_check = noerror; |
| funcentry->linenumber = linenum; |
| funcentry->file = file; |
| return 0; |
| } |
| |
| |
| int |
| newalias(char *unitname, char *unitdef,int linenum, char *file,FILE *errfile) |
| { |
| struct wantalias *aliasentry; |
| |
| if (!strchr(unitdef, UNITSEPCHAR)){ |
| if (errfile) fprintf(errfile, |
| "%s: unit list missing '%c' on line %d of '%s'\n", |
| progname, UNITSEPCHAR, linenum, file); |
| return E_BADFILE; |
| } |
| if ((aliasentry=aliaslookup(unitname))){ /* duplicate alias */ |
| if (flags.unitcheck && errfile) |
| fprintf(errfile, |
| "%s: unit list '%s' defined on line %d of '%s' is redefined on line %d of '%s'.\n", |
| progname, unitname, aliasentry->linenumber, |
| aliasentry->file, linenum, file); |
| free(aliasentry->definition); |
| } else { |
| aliasentry = (struct wantalias *) |
| mymalloc(sizeof(struct wantalias),"(newalias)"); |
| aliasentry->name = dupstr(unitname); |
| aliasentry->next = 0; |
| *aliaslistend = aliasentry; |
| aliaslistend = &aliasentry->next; |
| } |
| aliasentry->definition = dupstr(unitdef); |
| aliasentry->linenumber = linenum; |
| aliasentry->file = file; |
| return 0; |
| } |
| |
| |
| /* |
| Check environment variable name to see if its value appears on the |
| space delimited text string pointed to by list. Returns 2 if the |
| environment variable is not set, return 1 if its value appears on |
| the list and zero otherwise. |
| */ |
| |
| int |
| checkvar(char *name, char *list) |
| { |
| char *listitem; |
| name = getenv(name); |
| if (!name) |
| return 2; |
| listitem = strtok(list," "); |
| while (listitem){ |
| if (!strcmp(name, listitem)) |
| return 1; |
| listitem = strtok(0," "); |
| } |
| return 0; |
| } |
| |
| |
| #ifdef NO_SETENV |
| int |
| setenv(const char *name, const char *val, int overwrite) |
| { |
| char *environment; |
| |
| if (!overwrite && getenv(name) != NULL) |
| return 0; |
| environment = (char *) malloc(strlen(name) + strlen(val) + 2); |
| if (!environment) |
| return 1; |
| strcpy(environment, name); |
| strcat(environment, "="); |
| strcat(environment, val); |
| |
| /* putenv() doesn't copy its argument, so don't free environment */ |
| |
| #if defined (_WIN32) && defined (_MSC_VER) |
| return _putenv(environment); |
| #else |
| return putenv(environment); |
| #endif |
| } |
| #endif |
| |
| #ifdef _WIN32 |
| # define isdirsep(c) ((c) == '/' || (c) == '\\') |
| # define hasdirsep(s) strpbrk((s),"/\\") |
| #else |
| # define isdirsep(c) ((c) == '/') |
| # define hasdirsep(s) strchr((s),'/') |
| #endif |
| #define isexe(s) ((strlen(s) == 4) && (tolower(s[1]) == 'e') \ |
| && (tolower(s[2]) == 'x') && (tolower(s[3]) == 'e')) |
| |
| /* Returns a pointer to the end of the pathname part of the |
| specified filename */ |
| |
| char * |
| pathend(char *filename) |
| { |
| char *pointer; |
| |
| for(pointer=filename+strlen(filename);pointer>filename;pointer--){ |
| if (isdirsep(*pointer)) { |
| pointer++; |
| break; |
| } |
| } |
| return pointer; |
| } |
| |
| int |
| isfullpath(char *path) |
| { |
| #ifdef _WIN32 |
| /* handle Windows drive specifier */ |
| if (isalpha(*path) && *(path + 1) == ':') |
| path += 2; |
| #endif |
| return isdirsep(*path); |
| } |
| |
| /* |
| Read in units data. |
| |
| file - Filename to load |
| errfile - File to receive messages about errors in the units database. |
| Set it to 0 to suppress errors. |
| unitcount, prefixcount, funccount - Return statistics to the caller. |
| Must initialize to zero before calling. |
| depth - Used to prevent recursive includes. Call with it set to zero. |
| |
| The global variable progname is used in error messages. |
| */ |
| |
| int |
| readunits(char *file, FILE *errfile, |
| int *unitcount, int *prefixcount, int *funccount, int depth) |
| { |
| FILE *unitfile; |
| char *line = 0, *lineptr, *unitdef, *unitname, *permfile; |
| int linenum, linebufsize, goterr, retcode; |
| int locunitcount, locprefixcount, locfunccount, redefinition; |
| int wronglocale = 0; /* If set then we are currently reading data */ |
| int inlocale = 0; /* for the wrong locale so we should skip it */ |
| int in_utf8 = 0; /* If set we are reading utf8 data */ |
| int invar = 0; /* If set we are in data for an env variable.*/ |
| int wrongvar = 0; /* If set then we are not processing */ |
| |
| locunitcount = 0; |
| locprefixcount = 0; |
| locfunccount = 0; |
| linenum = 0; |
| linebufsize = 0; |
| goterr = 0; |
| |
| unitfile = openfile(file, "rt"); |
| |
| if (!unitfile){ |
| if (errfile) |
| fprintf(errfile, "%s: Unable to read units file '%s': %s\n", progname, file, strerror(errno)); |
| return E_FILE; |
| } |
| growbuffer(&line,&linebufsize); |
| /* coverity[alloc_fn] */ |
| permfile = dupstr(file); /* This is a permanent copy to reference in */ |
| /* the database. It is never freed. */ |
| while (!feof(unitfile)) { |
| if (!fgetslong(&line, &linebufsize, unitfile, &linenum)) |
| break; |
| if (linenum==1 && startswith(line, UTF8MARKER)){ |
| int i; |
| for(lineptr=line,i=0;i<strlen(UTF8MARKER);i++, lineptr++) |
| *lineptr=' '; |
| } |
| strip_comment(line); |
| if (-1 == strwidth(line)){ |
| readerror(errfile, "%s: %s on line %d of '%s'\n", |
| progname, invalid_utf8, linenum, file); |
| continue; |
| } |
| replace_operators(line); |
| |
| if (*line == COMMANDCHAR) { /* Process units file commands */ |
| unitname = strtok(line+1, " "); |
| if (!unitname){ |
| readerror(errfile, VAGUE_ERR); |
| continue; |
| } |
| |
| /* Check for locale and utf8 declarations. Any other commands |
| most likely belong after these tests. */ |
| if (!strcmp(unitname,"var") || !strcmp(unitname,"varnot")){ |
| int not = 0; |
| if (unitname[3]=='n') |
| not = 1; |
| unitname=strtok(0," "); |
| unitdef=strtok(0,""); /* Get rest of the line */ |
| if (!unitname) |
| readerror(errfile, |
| "%s: no variable name specified on line %d of '%s'\n", |
| progname, linenum, file); |
| else if (!unitdef) |
| readerror(errfile, |
| "%s: no value specified on line %d of '%s'\n", |
| progname, linenum, file); |
| else if (invar) |
| readerror(errfile, |
| "%s: nested var statements not allowed, line %d of '%s'\n", |
| progname, linenum, file); |
| else { |
| int check; |
| invar = 1; |
| check = checkvar(unitname, unitdef); |
| if (check==2){ |
| readerror(errfile, |
| "%s: environment variable %s not set at line %d of '%s'\n", |
| progname, unitname, linenum, file); |
| wrongvar = 1; |
| } |
| else if (!(not^check)) |
| wrongvar = 1; |
| } |
| continue; |
| } |
| else if (!strcmp(unitname, "endvar")){ |
| if (!invar) |
| readerror(errfile, |
| "%s: unmatched !endvar on line %d of '%s'\n", |
| progname, linenum, file); |
| wrongvar = 0; |
| invar = 0; |
| continue; |
| } |
| else if (!strcmp(unitname,"locale")){ |
| unitname = strtok(0, " "); |
| if (!unitname) |
| readerror(errfile, |
| "%s: no locale specified on line %d of '%s'\n", |
| progname, linenum, file); |
| else if (inlocale) |
| readerror(errfile, |
| "%s: nested locales not allowed, line %d of '%s'\n", |
| progname, linenum, file); |
| else { |
| inlocale = 1; |
| if (strcmp(unitname,mylocale)) /* locales don't match */ |
| wronglocale = 1; |
| } |
| continue; |
| } |
| else if (!strcmp(unitname, "endlocale")){ |
| if (!inlocale) |
| readerror(errfile, |
| "%s: unmatched !endlocale on line %d of '%s'\n", |
| progname, linenum, file); |
| wronglocale = 0; |
| inlocale = 0; |
| continue; |
| } |
| else if (!strcmp(unitname, "utf8")){ |
| if (in_utf8) |
| readerror(errfile,"%s: nested utf8 not allowed, line %d of '%s'\n", |
| progname, linenum, file); |
| else in_utf8 = 1; |
| continue; |
| } |
| else if (!strcmp(unitname, "endutf8")){ |
| if (!in_utf8) |
| readerror(errfile,"%s: unmatched !endutf8 on line %d of '%s'\n", |
| progname, linenum, file); |
| in_utf8 = 0; |
| continue; |
| } |
| if (in_utf8 && !utf8mode) continue; |
| if (wronglocale || wrongvar) continue; |
| |
| if (!strcmp(unitname,"prompt")){ |
| unitname = strtok(0,""); /* Rest of the line */ |
| if (promptprefix) |
| free(promptprefix); |
| if (!unitname) |
| promptprefix=0; |
| else |
| promptprefix = dupstr(unitname); |
| continue; |
| } |
| if (!strcmp(unitname,"message")){ |
| unitname = strtok(0,""); /* Rest of the line */ |
| if (!flags.quiet){ |
| if (unitname) logputs(unitname); |
| logputchar('\n'); |
| } |
| continue; |
| } |
| else if (!strcmp(unitname,"set")) { |
| unitname = strtok(0," "); |
| unitdef = strtok(0," "); |
| if (!unitname) |
| readerror(errfile, |
| "%s: no variable name specified on line %d of '%s'\n", |
| progname, linenum, file); |
| else if (!unitdef) |
| readerror(errfile, |
| "%s: no value specified on line %d of '%s'\n", |
| progname, linenum, file); |
| else |
| setenv(unitname, unitdef, 0); |
| continue; |
| } |
| else if (!strcmp(unitname,"unitlist")){ |
| splitline(0,&unitname, &unitdef); /* 0 continues strtok call */ |
| if (!unitname || !unitdef) |
| readerror(errfile,VAGUE_ERR); |
| else { |
| if (newalias(unitname, unitdef, linenum, permfile, errfile)) |
| goterr = 1; |
| } |
| continue; |
| } |
| else if (!strcmp(unitname, "include")){ |
| if (depth>MAXINCLUDE){ |
| readerror(errfile, |
| "%s: max include depth of %d exceeded in file '%s' line %d\n", |
| progname, MAXINCLUDE, file, linenum); |
| } else { |
| int readerr; |
| char *includefile; |
| |
| unitname = strtok(0, " "); |
| if (!unitname){ |
| readerror(errfile, |
| "%s: missing include filename on line %d of '%s'\n", |
| progname, linenum, file); |
| continue; |
| } |
| includefile = mymalloc(strlen(file)+strlen(unitname)+1, "(readunits)"); |
| if (isfullpath(unitname)) |
| strcpy(includefile,unitname); |
| else { |
| strcpy(includefile,file); |
| strcpy(pathend(includefile), unitname); |
| } |
| |
| readerr = readunits(includefile, errfile, unitcount, prefixcount, |
| funccount, depth+1); |
| if (readerr == E_MEMORY){ |
| fclose(unitfile); |
| free(line); |
| free(includefile); |
| return readerr; |
| } |
| if (readerr == E_FILE) { |
| readerror(errfile, "%s: file was included at line %d of file '%s'\n", progname,linenum, file); |
| } |
| |
| if (readerr) |
| goterr = 1; |
| free(includefile); |
| } |
| } else /* not a valid command */ |
| readerror(errfile,VAGUE_ERR); |
| continue; |
| } |
| if (in_utf8 && !utf8mode) continue; |
| if (wronglocale || wrongvar) continue; |
| splitline(line, &unitname, &unitdef); |
| if (!unitname) continue; |
| |
| if (*unitname == REDEFCHAR){ |
| unitname++; |
| redefinition=1; |
| if (strlen(unitname)==0){ |
| readerror(errfile, |
| "%s: expecting name of unit to redefine after '+' at line %d of '%s'\n", |
| progname, linenum,file); |
| continue; |
| } |
| } else |
| redefinition=0; |
| if (!strcmp(unitname,"-")) { |
| readerror(errfile, |
| "%s: expecting prefix name before '-' at line %d of '%s'\n", |
| progname, linenum,file); |
| continue; |
| } |
| if (!unitdef){ |
| readerror(errfile, |
| "%s: unit '%s' lacks a definition at line %d of '%s'\n", |
| progname, unitname, linenum, file); |
| continue; |
| } |
| |
| if (lastchar(unitname) == '-'){ /* it's a prefix definition */ |
| if (newprefix(unitname,unitdef,&locprefixcount,linenum, |
| permfile,errfile,redefinition)) |
| goterr=1; |
| } |
| else if (strchr(unitname,'[')){ /* table definition */ |
| retcode=newtable(unitname,unitdef,&locfunccount,linenum, |
| permfile,errfile,redefinition); |
| if (retcode){ |
| if (retcode != E_BADFILE){ |
| fclose(unitfile); |
| free(line); |
| return retcode; |
| } |
| goterr=1; |
| } |
| } |
| else if (strchr(unitname,'(')){ /* function definition */ |
| if (newfunction(unitname,unitdef,&locfunccount,linenum, |
| permfile,errfile,redefinition)) |
| goterr = 1; |
| } |
| else { /* ordinary unit definition */ |
| if (newunit(unitname,unitdef,&locunitcount,linenum,permfile,errfile,redefinition,0)) |
| goterr = 1; |
| } |
| } |
| fclose(unitfile); |
| free(line); |
| if (unitcount) |
| *unitcount+=locunitcount; |
| if (prefixcount) |
| *prefixcount+=locprefixcount; |
| if (funccount) |
| *funccount+=locfunccount; |
| if (goterr) |
| return E_BADFILE; |
| else return 0; |
| } |
| |
| /* Initialize a unit to be equal to 1. */ |
| |
| void |
| initializeunit(struct unittype *theunit) |
| { |
| theunit->factor = 1.0; |
| theunit->numerator[0] = theunit->denominator[0] = NULL; |
| } |
| |
| |
| /* Free a unit: frees all the strings used in the unit structure. |
| Does not free the unit structure itself. */ |
| |
| void |
| freeunit(struct unittype *theunit) |
| { |
| char **ptr; |
| |
| for(ptr = theunit->numerator; *ptr; ptr++) |
| if (*ptr != NULLUNIT) free(*ptr); |
| for(ptr = theunit->denominator; *ptr; ptr++) |
| if (*ptr != NULLUNIT) free(*ptr); |
| |
| /* protect against repeated calls to freeunit() */ |
| |
| theunit->numerator[0] = 0; |
| theunit->denominator[0] = 0; |
| } |
| |
| |
| /* Print out a unit */ |
| |
| void |
| showunit(struct unittype *theunit) |
| { |
| char **ptr; |
| int printedslash; |
| int counter = 1; |
| |
| logprintf(num_format.format, theunit->factor); |
| |
| for (ptr = theunit->numerator; *ptr; ptr++) { |
| if (ptr > theunit->numerator && **ptr && |
| !strcmp(*ptr, *(ptr - 1))) |
| counter++; |
| else { |
| if (counter > 1) |
| logprintf("%s%d", powerstring, counter); |
| if (**ptr) |
| logprintf(" %s", *ptr); |
| counter = 1; |
| } |
| } |
| if (counter > 1) |
| logprintf("%s%d", powerstring, counter); |
| counter = 1; |
| printedslash = 0; |
| for (ptr = theunit->denominator; *ptr; ptr++) { |
| if (ptr > theunit->denominator && **ptr && |
| !strcmp(*ptr, *(ptr - 1))) |
| counter++; |
| else { |
| if (counter > 1) |
| logprintf("%s%d", powerstring, counter); |
| if (**ptr) { |
| if (!printedslash) |
| logprintf(" /"); |
| printedslash = 1; |
| logprintf(" %s", *ptr); |
| } |
| counter = 1; |
| } |
| } |
| if (counter > 1) |
| logprintf("%s%d", powerstring, counter); |
| } |
| |
| |
| /* qsort comparison function */ |
| |
| int |
| compare(const void *item1, const void *item2) |
| { |
| return strcmp(*(char **) item1, *(char **) item2); |
| } |
| |
| /* Sort numerator and denominator of a unit so we can compare different |
| units */ |
| |
| void |
| sortunit(struct unittype *theunit) |
| { |
| char **ptr; |
| int count; |
| |
| for (count = 0, ptr = theunit->numerator; *ptr; ptr++, count++); |
| qsort(theunit->numerator, count, sizeof(char *), compare); |
| for (count = 0, ptr = theunit->denominator; *ptr; ptr++, count++); |
| qsort(theunit->denominator, count, sizeof(char *), compare); |
| } |
| |
| |
| /* Cancels duplicate units that appear in the numerator and |
| denominator. The input unit must be sorted. */ |
| |
| void |
| cancelunit(struct unittype *theunit) |
| { |
| char **den, **num; |
| int comp; |
| |
| den = theunit->denominator; |
| num = theunit->numerator; |
| |
| while (*num && *den) { |
| comp = strcmp(*den, *num); |
| if (!comp) { /* units match, so cancel them */ |
| if (*den!=NULLUNIT) free(*den); |
| if (*num!=NULLUNIT) free(*num); |
| *den++ = NULLUNIT; |
| *num++ = NULLUNIT; |
| } else if (comp < 0) /* Move up whichever pointer is alphabetically */ |
| den++; /* behind to look for future matches */ |
| else |
| num++; |
| } |
| } |
| |
| |
| /* |
| Looks up the definition for the specified unit including prefix processing |
| and plural removal. |
| |
| Returns a pointer to the definition or a null pointer |
| if the specified unit does not appear in the units table. |
| |
| Sometimes the returned pointer will be a pointer to the special |
| buffer created to hold the data. This buffer grows as needed during |
| program execution. |
| |
| Note that if you pass the output of lookupunit() back into the function |
| again you will get correct results, but the data you passed in may get |
| clobbered if it happened to be the internal buffer. |
| */ |
| |
| static int bufsize=0; |
| static char *buffer; /* buffer for lookupunit answers with prefixes */ |
| |
| |
| /* |
| Plural rules for english: add -s |
| after x, sh, ch, ss add -es |
| -y becomes -ies except after a vowel when you just add -s as usual |
| */ |
| |
| |
| char * |
| lookupunit(char *unit,int prefixok) |
| { |
| char *copy; |
| struct prefixlist *pfxptr; |
| struct unitlist *uptr; |
| |
| if ((uptr = ulookup(unit))) |
| return uptr->value; |
| |
| if (strwidth(unit)>2 && lastchar(unit) == 's') { |
| copy = dupstr(unit); |
| lastchar(copy) = 0; |
| if (lookupunit(copy,prefixok)){ |
| while(strlen(copy)+1 > bufsize) { |
| growbuffer(&buffer, &bufsize); |
| } |
| strcpy(buffer, copy); /* Note: returning looked up result seems */ |
| free(copy); /* better but it causes problems when it */ |
| return buffer; /* contains PRIMITIVECHAR. */ |
| } |
| if (strlen(copy)>2 && lastchar(copy) == 'e') { |
| lastchar(copy) = 0; |
| if (lookupunit(copy,prefixok)){ |
| while (strlen(copy)+1 > bufsize) { |
| growbuffer(&buffer,&bufsize); |
| } |
| strcpy(buffer,copy); |
| free(copy); |
| return buffer; |
| } |
| } |
| if (strlen(copy)>2 && lastchar(copy) == 'i') { |
| lastchar(copy) = 'y'; |
| if (lookupunit(copy,prefixok)){ |
| while (strlen(copy)+1 > bufsize) { |
| growbuffer(&buffer,&bufsize); |
| } |
| strcpy(buffer,copy); |
| free(copy); |
| return buffer; |
| } |
| } |
| free(copy); |
| } |
| if (prefixok && (pfxptr = plookup(unit))) { |
| copy = unit + pfxptr->len; |
| if (emptystr(copy) || lookupunit(copy,0)) { |
| char *tempbuf; |
| while (strlen(pfxptr->value)+strlen(copy)+2 > bufsize){ |
| growbuffer(&buffer, &bufsize); |
| } |
| tempbuf = dupstr(copy); /* copy might point into buffer */ |
| strcpy(buffer, pfxptr->value); |
| strcat(buffer, " "); |
| strcat(buffer, tempbuf); |
| free(tempbuf); |
| return buffer; |
| } |
| } |
| return 0; |
| } |
| |
| |
| /* Points entries of product[] to the strings stored in tomove[]. |
| Leaves tomove pointing to a list of NULLUNITS. */ |
| |
| int |
| moveproduct(char *product[], char *tomove[]) |
| { |
| char **dest, **src; |
| |
| dest=product; |
| for(src = tomove; *src; src++){ |
| if (*src == NULLUNIT) continue; |
| for(; *dest && *dest != NULLUNIT; dest++); |
| if (dest - product >= MAXSUBUNITS - 1) { |
| return E_PRODOVERFLOW; |
| } |
| if (!*dest) |
| *(dest + 1) = 0; |
| *dest = *src; |
| *src=NULLUNIT; |
| } |
| return 0; |
| } |
| |
| /* |
| Make a copy of a product list. Note that no error checking is done |
| for overflowing the product list because it is assumed that the |
| source list doesn't overflow, so the destination list shouldn't |
| overflow either. (This assumption could be false if the |
| destination is not actually at the start of a product buffer.) |
| */ |
| |
| void |
| copyproduct(char **dest, char **source) |
| { |
| for(;*source;source++,dest++) { |
| if (*source==NULLUNIT) |
| *dest = NULLUNIT; |
| else |
| *dest=dupstr(*source); |
| } |
| *dest=0; |
| } |
| |
| /* Make a copy of a unit */ |
| |
| void |
| unitcopy(struct unittype *dest, struct unittype *source) |
| { |
| dest->factor = source->factor; |
| copyproduct(dest->numerator, source->numerator); |
| copyproduct(dest->denominator, source->denominator); |
| } |
| |
| |
| /* Multiply left by right. In the process, all of the units are |
| deleted from right (but it is not freed) */ |
| |
| int |
| multunit(struct unittype *left, struct unittype *right) |
| { |
| int myerr; |
| left->factor *= right->factor; |
| myerr = moveproduct(left->numerator, right->numerator); |
| if (!myerr) |
| myerr = moveproduct(left->denominator, right->denominator); |
| return myerr; |
| } |
| |
| int |
| divunit(struct unittype *left, struct unittype *right) |
| { |
| int myerr; |
| left->factor /= right->factor; |
| myerr = moveproduct(left->numerator, right->denominator); |
| if (!myerr) |
| myerr = moveproduct(left->denominator, right->numerator); |
| return myerr; |
| } |
| |
| |
| /* |
| reduces a product of symbolic units to primitive units. |
| The three low bits are used to return flags: |
| |
| bit 0 set if reductions were performed without error. |
| bit 1 set if no reductions are performed. |
| bit 2 set if an unknown unit is discovered. |
| |
| Return values from multiple calls will be ORed together later. |
| */ |
| |
| #define DIDREDUCTION (1<<0) |
| #define NOREDUCTION (1<<1) |
| #define REDUCTIONERROR (1<<2) |
| #define CIRCULARDEF (1<<3) |
| |
| int |
| reduceproduct(struct unittype *theunit, int flip) |
| { |
| |
| char *toadd; |
| char **product; |
| int didsomething = NOREDUCTION; |
| struct unittype newunit; |
| int ret; |
| int itcount=0; /* Count iterations to catch infinite loops */ |
| |
| if (flip) |
| product = theunit->denominator; |
| else |
| product = theunit->numerator; |
| |
| for (; *product; product++) { |
| for (;;) { |
| if (!strlen(*product)) |
| break; |
| |
| /* check for infinite loop */ |
| itcount++; |
| if (itcount>MAXPRODUCTREDUCTIONS) |
| return CIRCULARDEF; |
| |
| toadd = lookupunit(*product,1); |
| if (!toadd) { |
| if (!irreducible) |
| irreducible = dupstr(*product); |
| return REDUCTIONERROR; |
| } |
| if (strchr(toadd, PRIMITIVECHAR)) |
| break; |
| didsomething = DIDREDUCTION; |
| if (*product != NULLUNIT) { |
| free(*product); |
| *product = NULLUNIT; |
| } |
| if (parseunit(&newunit, toadd, 0, 0)) |
| return REDUCTIONERROR; |
| if (flip) ret=divunit(theunit,&newunit); |
| else ret=multunit(theunit,&newunit); |
| freeunit(&newunit); |
| if (ret) |
| return REDUCTIONERROR; |
| } |
| } |
| return didsomething; |
| } |
| |
| |
| #if 0 |
| void showunitdetail(struct unittype *foo) |
| { |
| char **ptr; |
| |
| printf("%.17g ", foo->factor); |
| |
| for(ptr=foo->numerator;*ptr;ptr++) |
| if (*ptr==NULLUNIT) printf("NULL "); |
| else printf("`%s' ", *ptr); |
| printf(" / "); |
| for(ptr=foo->denominator;*ptr;ptr++) |
| if (*ptr==NULLUNIT) printf("NULL "); |
| else printf("`%s' ", *ptr); |
| putchar('\n'); |
| } |
| #endif |
| |
| |
| /* |
| Reduces numerator and denominator of the specified unit. |
| Returns 0 on success, or 1 on unknown unit error. |
| */ |
| |
| int |
| reduceunit(struct unittype *theunit) |
| { |
| int ret; |
| |
| if (irreducible) |
| free(irreducible); |
| irreducible=0; |
| ret = DIDREDUCTION; |
| |
| /* Keep calling reduceproduct until it doesn't do anything */ |
| |
| while (ret & DIDREDUCTION) { |
| ret = reduceproduct(theunit, 0); |
| if (!(ret & REDUCTIONERROR)) |
| ret |= reduceproduct(theunit, 1); |
| if (ret & REDUCTIONERROR){ |
| if (irreducible) |
| return E_UNKNOWNUNIT; |
| else |
| return E_REDUCE; |
| } |
| else if (ret & CIRCULARDEF) |
| return E_CIRCULARDEF; |
| } |
| return 0; |
| } |
| |
| /* |
| Returns one if the argument unit is defined in the data file as a |
| dimensionless unit. This is determined by comparing its definition to |
| the string NODIM. |
| */ |
| |
| int |
| ignore_dimless(char *name) |
| { |
| struct unitlist *ul; |
| if (!name) |
| return 0; |
| ul = ulookup(name); |
| if (ul && !strcmp(ul->value, NODIM)) |
| return 1; |
| return 0; |
| } |
| |
| int |
| ignore_nothing(char *name) |
| { |
| return 0; |
| } |
| |
| |
| int |
| ignore_primitive(char *name) |
| { |
| struct unitlist *ul; |
| if (!name) |
| return 0; |
| ul = ulookup(name); |
| if (ul && strchr(ul->value, PRIMITIVECHAR)) |
| return 1; |
| return 0; |
| } |
| |
| |
| /* |
| Compare two product lists, return zero if they match and one if |
| they do not match. They may contain embedded NULLUNITs which are |
| ignored in the comparison. Units defined as NODIM are also ignored |
| in the comparison. |
| */ |
| |
| int |
| compareproducts(char **one, char **two, int (*isdimless)(char *name)) |
| { |
| int oneblank, twoblank; |
| while (*one || *two) { |
| oneblank = (*one==NULLUNIT) || isdimless(*one); |
| twoblank = (*two==NULLUNIT) || isdimless(*two); |
| if (!*one && !twoblank) |
| return 1; |
| if (!*two && !oneblank) |
| return 1; |
| if (oneblank) |
| one++; |
| else if (twoblank) |
| two++; |
| else if (strcmp(*one, *two)) |
| return 1; |
| else |
| one++, two++; |
| } |
| return 0; |
| } |
| |
| |
| /* Return zero if units are compatible, nonzero otherwise. The units |
| must be reduced, sorted and canceled for this to work. */ |
| |
| int |
| compareunits(struct unittype *first, struct unittype *second, |
| int (*isdimless)(char *name)) |
| { |
| return |
| compareproducts(first->numerator, second->numerator, isdimless) || |
| compareproducts(first->denominator, second->denominator, isdimless); |
| } |
| |
| |
| /* Reduce a unit as much as possible */ |
| |
| int |
| completereduce(struct unittype *unit) |
| { |
| int err; |
| |
| if ((err=reduceunit(unit))) |
| return err; |
| |
| sortunit(unit); |
| cancelunit(unit); |
| return 0; |
| } |
| |
| |
| /* Raise theunit to the specified power. This function does not fill |
| in NULLUNIT gaps, which could be considered a deficiency. */ |
| |
| int |
| expunit(struct unittype *theunit, int power) |
| { |
| char **numptr, **denptr; |
| double thefactor; |
| int i, uind, denlen, numlen; |
| |
| if (power==0){ |
| freeunit(theunit); |
| initializeunit(theunit); |
| return 0; |
| } |
| numlen=0; |
| for(numptr=theunit->numerator;*numptr;numptr++) numlen++; |
| denlen=0; |
| for(denptr=theunit->denominator;*denptr;denptr++) denlen++; |
| thefactor=theunit->factor; |
| for(i=1;i<power;i++){ |
| theunit->factor *= thefactor; |
| for(uind=0;uind<numlen;uind++){ |
| if (theunit->numerator[uind]!=NULLUNIT){ |
| if (numptr-theunit->numerator>=MAXSUBUNITS-1) { |
| *numptr=*denptr=0; |
| return E_PRODOVERFLOW; |
| } |
| *numptr++=dupstr(theunit->numerator[uind]); |
| } |
| } |
| for(uind=0;uind<denlen;uind++){ |
| if (theunit->denominator[uind]!=NULLUNIT){ |
| *denptr++=dupstr(theunit->denominator[uind]); |
| if (denptr-theunit->denominator>=MAXSUBUNITS-1) { |
| *numptr=*denptr=0; |
| return E_PRODOVERFLOW; |
| } |
| } |
| } |
| } |
| *numptr=0; |
| *denptr=0; |
| return 0; |
| } |
| |
| |
| int |
| unit2num(struct unittype *input) |
| { |
| struct unittype one; |
| int err; |
| |
| initializeunit(&one); |
| if ((err=completereduce(input))) |
| return err; |
| if (compareunits(input,&one,ignore_nothing)) |
| return E_NOTANUMBER; |
| freeunit(input); |
| return 0; |
| } |
| |
| |
| int |
| unitdimless(struct unittype *input) |
| { |
| struct unittype one; |
| initializeunit(&one); |
| if (compareunits(input, &one, ignore_dimless)) |
| return 0; |
| freeunit(input); /* Eliminate dimensionless units from list */ |
| return 1; |
| } |
| |
| |
| /* |
| The unitroot function takes the nth root of an input unit which has |
| been completely reduced. Returns 1 if the unit is not a power of n. |
| Input data can contain NULLUNITs. |
| */ |
| |
| int |
| subunitroot(int n,char *current[], char *out[]) |
| { |
| char **ptr; |
| int count=0; |
| |
| while(*current==NULLUNIT) current++; /* skip past NULLUNIT entries */ |
| ptr=current; |
| while(*ptr){ |
| while(*ptr){ |
| if (*ptr!=NULLUNIT){ |
| if (strcmp(*current,*ptr)) break; |
| count++; |
| } |
| ptr++; |
| } |
| if (count % n != 0){ /* If not dimensionless generate error, otherwise */ |
| if (!ignore_dimless(*current)) /* just skip over it */ |
| return E_NOTROOT; |
| } else { |
| for(count /= n;count>0;count--) *(out++) = dupstr(*current); |
| } |
| current=ptr; |
| } |
| *out = 0; |
| return 0; |
| } |
| |
| |
| int |
| rootunit(struct unittype *inunit,int n) |
| { |
| struct unittype outunit; |
| int err; |
| |
| initializeunit(&outunit); |
| if ((err=completereduce(inunit))) |
| return err; |
| /* Roots of negative numbers fail in pow(), even odd roots */ |
| if (inunit->factor < 0) |
| return E_NOTROOT; |
| outunit.factor = pow(inunit->factor,1.0/(double)n); |
| if ((err = subunitroot(n, inunit->numerator, outunit.numerator))) |
| return err; |
| if ((err = subunitroot(n, inunit->denominator, outunit.denominator))) |
| return err; |
| freeunit(inunit); |
| initializeunit(inunit); |
| return multunit(inunit,&outunit); |
| } |
| |
| |
| /* Compute the inverse of a unit (1/theunit) */ |
| |
| void |
| invertunit(struct unittype *theunit) |
| { |
| char **ptr, *swap; |
| int numlen, length, ind; |
| |
| theunit->factor = 1.0/theunit->factor; |
| length=numlen=0; |
| for(ptr=theunit->denominator;*ptr;ptr++,length++); |
| for(ptr=theunit->numerator;*ptr;ptr++,numlen++); |
| if (numlen>length) |
| length=numlen; |
| for(ind=0;ind<=length;ind++){ |
| swap = theunit->numerator[ind]; |
| theunit->numerator[ind] = theunit->denominator[ind]; |
| theunit->denominator[ind] = swap; |
| } |
| } |
| |
| |
| int |
| float2rat(double y, int *p, int *q) |
| { |
| int coef[20]; /* How long does this buffer need to be? */ |
| int i,termcount,saveq; |
| double fracpart,x; |
| |
| /* Compute continued fraction approximation */ |
| |
| x=y; |
| termcount=0; |
| while(1){ |
| coef[termcount] = (int) floor(x); |
| fracpart = x-coef[termcount]; |
| if (fracpart < .001 || termcount==19) break; |
| x = 1/fracpart; |
| termcount++; |
| } |
| |
| /* Compress continued fraction into rational p/q */ |
| |
| *p=0; |
| *q=1; |
| for(i=termcount;i>=1;i--) { |
| saveq=*q; |
| *q = coef[i] * *q + *p; |
| *p = saveq; |
| } |
| *p+=*q*coef[0]; |
| return *q<MAXSUBUNITS && fabs((double)*p / (double)*q - y) < DBL_EPSILON; |
| } |
| |
| |
| /* Raise a unit to a power */ |
| |
| int |
| unitpower(struct unittype *base, struct unittype *exponent) |
| { |
| int errcode, p, q; |
| |
| errcode = unit2num(exponent); |
| if (errcode == E_NOTANUMBER) |
| return E_DIMEXPONENT; |
| if (errcode) |
| return errcode; |
| errcode = unit2num(base); |
| if (!errcode){ /* Exponent base is dimensionless */ |
| base->factor = pow(base->factor,exponent->factor); |
| if (errno) |
| return E_FUNC; |
| } |
| else if (errcode==E_NOTANUMBER) { /* Base not dimensionless */ |
| if (!float2rat(exponent->factor,&p,&q)){ /* Exponent must be rational */ |
| if (unitdimless(base)){ |
| base->factor = pow(base->factor,exponent->factor); |
| if (errno) |
| return E_FUNC; |
| } |
| else |
| return E_IRRATIONAL_EXPONENT; |
| } else { |
| if (q!=1) { |
| errcode = rootunit(base, q); |
| if (errcode == E_NOTROOT) |
| return E_BASE_NOTROOT; |
| if (errcode) |
| return errcode; |
| } |
| errcode = expunit(base, abs(p)); |
| if (errcode) |
| return errcode; |
| if (p<0) |
| invertunit(base); |
| } |
| } |
| else return errcode; |
| return 0; |
| } |
| |
| |
| /* Old units program would give message about what each operand |
| reduced to, showing that they weren't conformable. Can this |
| be achieved here? */ |
| |
| int |
| addunit(struct unittype *unita, struct unittype *unitb) |
| { |
| int err; |
| |
| if ((err=completereduce(unita))) |
| return err; |
| if ((err=completereduce(unitb))) |
| return err; |
| if (compareunits(unita,unitb,ignore_nothing)) |
| return E_BADSUM; |
| unita->factor += unitb->factor; |
| freeunit(unitb); |
| return 0; |
| } |
| |
| |
| double |
| linearinterp(double a, double b, double aval, double bval, double c) |
| { |
| double lambda; |
| |
| lambda = (b-c)/(b-a); |
| return lambda*aval + (1-lambda)*bval; |
| } |
| |
| |
| /* evaluate a user function */ |
| |
| #define INVERSE 1 |
| #define FUNCTION 0 |
| #define ALLERR 1 |
| #define NORMALERR 0 |
| |
| int |
| evalfunc(struct unittype *theunit, struct func *infunc, int inverse, |
| int allerrors) |
| { |
| struct unittype result; |
| struct functype *thefunc; |
| int err; |
| double value; |
| int foundit, count; |
| struct unittype *save_value; |
| char *save_function; |
| |
| if (infunc->table) { /* Tables are short, so use dumb search algorithm */ |
| err = parseunit(&result, infunc->tableunit, 0, 0); |
| if (err) |
| return E_BADFUNCDIMEN; |
| if (inverse){ |
| err = divunit(theunit, &result); |
| if (err) |
| return err; |
| err = unit2num(theunit); |
| if (err==E_NOTANUMBER) |
| return E_BADFUNCARG; |
| if (err) |
| return err; |
| value = theunit->factor; |
| foundit=0; |
| for(count=0;count<infunc->tablelen-1;count++) |
| if ((infunc->table[count].value<=value && |
| value<=infunc->table[count+1].value) || |
| (infunc->table[count+1].value<=value && |
| value<=infunc->table[count].value)){ |
| foundit=1; |
| value = linearinterp(infunc->table[count].value, |
| infunc->table[count+1].value, |
| infunc->table[count].location, |
| infunc->table[count+1].location, |
| value); |
| break; |
| } |
| if (!foundit) |
| return E_NOTINDOMAIN; |
| freeunit(&result); |
| freeunit(theunit); |
| theunit->factor = value; |
| return 0; |
| } else { |
| err=unit2num(theunit); |
| if (err) |
| return err; |
| value=theunit->factor; |
| foundit=0; |
| for(count=0;count<infunc->tablelen-1;count++) |
| if (infunc->table[count].location<=value && |
| value<=infunc->table[count+1].location){ |
| foundit=1; |
| value = linearinterp(infunc->table[count].location, |
| infunc->table[count+1].location, |
| infunc->table[count].value, |
| infunc->table[count+1].value, |
| value); |
| break; |
| } |
| if (!foundit) |
| return E_NOTINDOMAIN; |
| result.factor *= value; |
| } |
| } else { /* it's a function */ |
| if (inverse){ |
| thefunc=&(infunc->inverse); |
| if (!thefunc->def) |
| return E_NOINVERSE; |
| } |
| else |
| thefunc=&(infunc->forward); |
| err = completereduce(theunit); |
| if (err) |
| return err; |
| if (thefunc->dimen){ |
| err = parseunit(&result, thefunc->dimen, 0, 0); |
| if (err) |
| return E_BADFUNCDIMEN; |
| err = completereduce(&result); |
| if (err) |
| return E_BADFUNCDIMEN; |
| if (compareunits(&result, theunit, ignore_nothing)) |
| return E_BADFUNCARG; |
| value = theunit->factor/result.factor; |
| } else |
| value = theunit->factor; |
| if (thefunc->domain_max && |
| (value > *thefunc->domain_max || |
| (thefunc->domain_max_open && value == *thefunc->domain_max))) |
| return E_NOTINDOMAIN; |
| if (thefunc->domain_min && |
| (value < *thefunc->domain_min || |
| (thefunc->domain_min_open && value == *thefunc->domain_min))) |
| return E_NOTINDOMAIN; |
| save_value = parameter_value; |
| save_function = function_parameter; |
| parameter_value = theunit; |
| function_parameter = thefunc->param; |
| err = parseunit(&result, thefunc->def, 0,0); |
| function_parameter = save_function; |
| parameter_value = save_value; |
| if (err && (allerrors == ALLERR || err==E_PARSEMEM || err==E_PRODOVERFLOW |
| || err==E_NOTROOT || err==E_BADFUNCTYPE)) |
| return err; |
| if (err) |
| return E_FUNARGDEF; |
| } |
| freeunit(theunit); |
| initializeunit(theunit); |
| multunit(theunit, &result); |
| return 0; |
| } |
| |
| |
| /* |
| append a formatted string to a buffer; first character of buffer |
| should be '\0' on first call |
| */ |
| size_t |
| vbufprintf(char **buf, size_t *bufsize, char *fmt, ...) |
| { |
| va_list args; |
| size_t |
| oldlen, /* length of current buffer contents */ |
| newlen, /* length of string to be added */ |
| buflen; /* length of new buffer contents */ |
| double growfactor = 1.5; |
| static char *newbuf = NULL; |
| char *oldbuf; |
| |
| oldlen = strlen(*buf); |
| oldbuf = dupstr(*buf); |
| |
| /* get length of formatted string to be appended to buffer */ |
| va_start(args, fmt); |
| newlen = vsnprintf(NULL, 0, fmt, args); |
| va_end(args); |
| |
| /* allocate a buffer for the new string */ |
| newbuf = (char *) malloc(newlen + 1); |
| if (newbuf == NULL) { |
| fprintf(stderr, "%s (bufprintf): memory allocation error\n", progname); |
| exit(EXIT_FAILURE); |
| } |
| |
| /* expand main buffer if necessary */ |
| if (*bufsize < oldlen + newlen + 1) { |
| *bufsize = (size_t) ((oldlen + newlen) * growfactor + 1); |
| |
| *buf = (char *) realloc(*buf, *bufsize); |
| if (*buf == NULL) { |
| fprintf(stderr, "%s (bufprintf): memory allocation error\n", progname); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| /* generate the new formatted string */ |
| va_start(args, fmt); |
| newlen = vsprintf(newbuf, fmt, args); |
| va_end(args); |
| |
| /* copy old and new strings to buffer */ |
| strcpy(*buf, oldbuf); |
| strcat(*buf, newbuf); |
| buflen = strlen(*buf); |
| |
| free(oldbuf); |
| free(newbuf); |
| |
| return buflen; |
| } |
| |
| |
| /* |
| similar to showunit(), but it saves the string in a buffer rather |
| than sending it to an output stream |
| */ |
| char * |
| buildunitstr(struct unittype *theunit) |
| { |
| char **ptr; |
| char *buf; |
| int printedslash; |
| int counter = 1; |
| size_t bufsize, newlen; |
| |
| bufsize = 256; |
| buf = (char *) mymalloc(bufsize, "(buildunitstr)"); |
| *buf = '\0'; |
| |
| /* factor */ |
| newlen = vbufprintf(&buf, &bufsize, num_format.format, theunit->factor); |
| |
| /* numerator */ |
| for (ptr = theunit->numerator; *ptr; ptr++) { |
| if (ptr > theunit->numerator && **ptr && |
| !strcmp(*ptr, *(ptr - 1))) |
| counter++; |
| else { |
| if (counter > 1) |
| newlen = vbufprintf(&buf, &bufsize, "%s%d", powerstring, counter); |
| if (**ptr) |
| newlen = vbufprintf(&buf, &bufsize, " %s", *ptr); |
| counter = 1; |
| } |
| } |
| if (counter > 1) |
| newlen = vbufprintf(&buf, &bufsize, "%s%d", powerstring, counter); |
| counter = 1; |
| printedslash = 0; |
| |
| /* denominator */ |
| for (ptr = theunit->denominator; *ptr; ptr++) { |
| if (ptr > theunit->denominator && **ptr && |
| !strcmp(*ptr, *(ptr - 1))) |
| counter++; |
| else { |
| if (counter > 1) |
| newlen = vbufprintf(&buf, &bufsize, "%s%d", powerstring, counter); |
| if (**ptr) { |
| if (!printedslash) |
| newlen = vbufprintf(&buf, &bufsize, " /"); |
| printedslash = 1; |
| newlen = vbufprintf(&buf, &bufsize, " %s", *ptr); |
| } |
| counter = 1; |
| } |
| } |
| if (counter > 1) |
| newlen = vbufprintf(&buf, &bufsize, "%s%d", powerstring, counter); |
| |
| return buf; |
| } |
| |
| /* |
| If the given character string has only one unit name in it, then print out |
| the rule for that unit. In any case, print out the reduced form for |
| the unit. |
| */ |
| |
| void |
| showdefinition(char *unitstr, struct unittype *theunit) |
| { |
| size_t bufsize = 256; |
| char *buf, *showstr; |
| |
| logputs(deftext); |
| showstr = buildunitstr(theunit); |
| buf = (char *) mymalloc(bufsize, "(showdefinition)"); |
| |
| while((unitstr = lookupunit(unitstr,1)) |
| && strspn(unitstr,digits) != strlen(unitstr) |
| && !strchr(unitstr,PRIMITIVECHAR)) { |
| if (strlen(unitstr) > bufsize - 1) { |
| bufsize = strlen(unitstr) + 1; |
| buf = realloc(buf, bufsize); |
| } |
| tightbufprint(buf, unitstr); |
| logputs(buf); |
| if (strcmp(buf, showstr)) |
| logputs(" = "); |
| } |
| |
| if (strcmp(buf, showstr)) |
| logputs(showstr); |
| logputchar('\n'); |
| free(buf); |
| free(showstr); |
| } |
| |
| |
| void |
| showfunction(struct functype *func) |
| { |
| struct unittype unit; |
| int not_dimensionless, i; |
| |
| if (!func->def) { |
| logputs(" is undefined"); |
| return; |
| } |
| |
| if (func->dimen){ /* coverity[check_return] */ |
| parseunit(&unit,func->dimen,0,0); /* unit2num returns 0 for */ |
| not_dimensionless = unit2num(&unit); /* dimensionless units */ |
| } |
| |
| logprintf("(%s) = %s", func->param, func->def); |
| if (func->domain_min || func->domain_max){ |
| logputchar('\n'); |
| for(i=strwidth(deftext);i;i--) logputchar(' '); |
| logputs("defined for "); |
| if (func->domain_min && func->domain_max) { |
| logprintf(num_format.format, *func->domain_min); |
| if (func->dimen && (not_dimensionless || unit.factor != 1)){ |
| if (isdecimal(*func->dimen)) |
| logputs(" *"); |
| logprintf(" %s",func->dimen); |
| } |
| logputs(func->domain_min_open?" < ":" <= "); |
| } |
| logputs(func->param); |
| if (func->domain_max){ |
| logputs(func->domain_max_open?" < ":" <= "); |
| logprintf(num_format.format, *func->domain_max); |
| } |
| else { |
| logputs(func->domain_min_open?" > ":" >= "); |
| logprintf(num_format.format, *func->domain_min); |
| } |
| if (func->dimen && (not_dimensionless || unit.factor != 1)){ |
| if (isdecimal(*func->dimen)) |
| logputs(" *"); |
| logprintf(" %s",func->dimen); |
| } |
| if (!func->dimen) logputs(" (any units)"); |
| } else if (func->dimen){ |
| logputchar('\n'); |
| for(i=strwidth(deftext);i;i--) logputchar(' '); |
| if (not_dimensionless) |
| logprintf("%s has units %s",func->param, func->dimen); |
| else |
| logprintf("%s is dimensionless",func->param); |
| } |
| logputchar('\n'); |
| } |
| |
| void |
| showtable(struct func *fun, int inverse) |
| { |
| int i; |
| |
| logprintf("%sinterpolated table with points\n",deftext); |
| if (inverse){ |
| int reverse, j; |
| reverse = (fun->table[0].value > fun->table[fun->tablelen-1].value); |
| for(i=0;i<fun->tablelen;i++){ |
| if (reverse) j = fun->tablelen-i-1; |
| else j=i; |
| if (flags.verbose>0) |
| logputs("\t\t "); |
| logprintf("~%s(", fun->name); |
| logprintf(num_format.format, fun->table[j].value); |
| if (isdecimal(fun->tableunit[0])) |
| logputs(" *"); |
| logprintf(" %s",fun->tableunit); |
| logputs(") = "); |
| logprintf(num_format.format, fun->table[j].location); |
| logputchar('\n'); |
| } |
| } else { |
| for(i=0;i<fun->tablelen;i++){ |
| if (flags.verbose>0) |
| logputs("\t\t "); |
| logprintf("%s(", fun->name); |
| logprintf(num_format.format, fun->table[i].location); |
| logputs(") = "); |
| logprintf(num_format.format, fun->table[i].value); |
| if (isdecimal(fun->tableunit[0])) |
| logputs(" *"); |
| logprintf(" %s\n",fun->tableunit); |
| } |
| } |
| } |
| |
| |
| void |
| showfuncdefinition(struct func *fun, int inverse) |
| { |
| if (fun->table) /* It's a table */ |
| showtable(fun, inverse); |
| else { |
| logprintf("%s%s%s", deftext,inverse?"~":"", fun->name); |
| if (inverse) |
| showfunction(&fun->inverse); |
| else |
| showfunction(&fun->forward); |
| } |
| } |
| |
| |
| void |
| showunitlistdef(struct wantalias *alias) |
| { |
| logprintf("%sunit list, ",deftext); |
| tightprint(stdout,alias->definition); |
| if (logfile) tightprint(logfile,alias->definition); |
| logputchar('\n'); |
| } |
| |
| |
| /* Show conversion to a function. Input unit 'have' is replaced by the |
| function inverse and completely reduced. */ |
| |
| int |
| showfunc(char *havestr, struct unittype *have, struct func *fun) |
| { |
| int err; |
| char *dimen; |
| |
| err = evalfunc(have, fun, INVERSE, NORMALERR); |
| if (!err) |
| err = completereduce(have); |
| if (err) { |
| if (err==E_BADFUNCARG){ |
| logputs("conformability error"); |
| if (fun->table) |
| dimen = fun->tableunit; |
| else if (fun->inverse.dimen) |
| dimen = fun->inverse.dimen; |
| else |
| dimen = 0; |
| if (!dimen) |
| logputchar('\n'); |
| else { |
| struct unittype want; |
| |
| if (emptystr(dimen)) |
| dimen = "1"; |
| logprintf(": conversion requires dimensions of '%s'\n",dimen); |
| if (flags.verbose==2) logprintf("\t%s = ",havestr); |
| else if (flags.verbose==1) logputchar('\t'); |
| showunit(have); |
| if (flags.verbose==2) logprintf("\n\t%s = ",dimen); |
| else if (flags.verbose==1) logprintf("\n\t"); |
| else logputchar('\n'); /* coverity[check_return] */ |
| parseunit(&want, dimen, 0, 0); /* coverity[check_return] */ |
| completereduce(&want); /* dimen was already checked for */ |
| showunit(&want); /* errors so no need to check here */ |
| logputchar('\n'); |
| } |
| } else if (err==E_NOTINDOMAIN) |
| logprintf("Value '%s' is not in the function's range\n",havestr); |
| else if (err==E_NOINVERSE) |
| logprintf("Inverse of the function '%s' is not defined\n",fun->name); |
| else |
| logputs("Function evaluation error (bad function definition)\n"); |
| return 1; |
| } |
| if (flags.verbose==2) |
| logprintf("\t%s = %s(", havestr, fun->name); |
| else if (flags.verbose==1) |
| logputchar('\t'); |
| showunit(have); |
| if (flags.verbose==2) |
| logputchar(')'); |
| logputchar('\n'); |
| return 0; |
| } |
| |
| /* Print the conformability error message */ |
| |
| void |
| showconformabilityerr(char *havestr,struct unittype *have, |
| char *wantstr,struct unittype *want) |
| { |
| logputs("conformability error\n"); |
| if (flags.verbose==2) |
| logprintf("\t%s = ",havestr); |
| else if (flags.verbose==1) |
| logputchar('\t'); |
| showunit(have); |
| if (flags.verbose==2) |
| logprintf("\n\t%s = ",wantstr); |
| else if (flags.verbose==1) |
| logputs("\n\t"); |
| else |
| logputchar('\n'); |
| showunit(want); |
| logputchar('\n'); |
| } /* end showconformabilityerr */ |
| |
| |
| |
| /* |
| determine whether a unit string begins with a fraction; assume it |
| does if it starts with an integer, '|', and another integer |
| */ |
| int |
| isfract(const char *unitstr) |
| { |
| char *enddouble=0, *endlong=0; |
| |
| while (isdigit(*unitstr)) |
| unitstr++; |
| if (*unitstr++ == '|') { |
| (void)strtod(unitstr, &enddouble); |
| (void)strtol(unitstr, &endlong, 10); |
| if (enddouble == endlong) |
| return 1; |
| } |
| return 0; |
| } |
| |
| int |
| checksigdigits(char *arg) |
| { |
| int errors, ival; |
| char *nonum; |
| |
| errors = 0; |
| |
| if (!strcmp(arg, "max")) |
| num_format.precision = MAXPRECISION; |
| else { |
| ival = (int) strtol(arg, &nonum, 10); |
| if (!emptystr(nonum)) { |
| fprintf(stderr, |
| "%s: invalid significant digits (%s)--integer value or 'max' required\n", |
| progname, arg); |
| errors++; |
| } |
| else if (ival <= 0) { |
| fprintf(stderr, "%s: number of significant digits must be positive\n", |
| progname); |
| errors++; |
| } |
| else if (ival > MAXPRECISION) { |
| fprintf(stderr, |
| "%s: too many significant digits (%d)--using maximum value (%d)\n", |
| progname, ival, MAXPRECISION); |
| num_format.precision = MAXPRECISION; |
| } |
| else |
| num_format.precision = ival; |
| } |
| if (errors) |
| return -1; |
| else |
| return 0; |
| } |
| |
| /* set output number format specification from significant digits and type */ |
| |
| int |
| setnumformat() |
| { |
| size_t len; |
| |
| if (strchr("Ee", num_format.type)) |
| num_format.precision--; |
| |
| len = 4; /* %, decimal point, type, terminating NUL */ |
| if (num_format.precision > 0) |
| len += (size_t) floor(log10((double) num_format.precision))+1; |
| num_format.format = (char *) mymalloc(len, "(setnumformat)"); |
| sprintf(num_format.format, "%%.%d%c", num_format.precision, num_format.type); |
| return 0; |
| } |
| |
| /* |
| parse and validate the output number format specification and |
| extract its type and precision into the num_format structure. |
| Returns nonzero for invalid format. |
| */ |
| |
| int |
| parsenumformat() |
| { |
| static char *format_types = NULL; |
| static char *format_flags = "+-# 0'"; |
| static char badflag; |
| char *two = "0x1p+1"; |
| char *valid="ABCDEFGHIJKLMNOPQRSTUVWXYXabcdefghijklmnopqrstuvwxyx.01234567890"; |
| char *dotptr, *lptr, *nonum, *p; |
| char testbuf[80]; |
| int errors, ndx; |
| |
| if (format_types == NULL){ |
| format_types = (char *) mymalloc(strlen(BASE_FORMATS)+4, "(parsenumformat)"); |
| strcpy(format_types,BASE_FORMATS); |
| |
| /* check for support of type 'F' (MS VS 2012 doesn't have it) */ |
| sprintf(testbuf, "%.1F", 1.2); |
| if (strlen(testbuf) == 3 && testbuf[0] == '1' && testbuf[2] == '2') |
| strcat(format_types,"F"); |
| |
| /* check for support of hexadecimal floating point */ |
| sprintf(testbuf, "%.0a", 2.0); |
| if (!strcmp(testbuf,two)) |
| strcat(format_types, "aA"); |
| |
| /* check for support of digit-grouping (') flag */ |
| sprintf(testbuf, "%'.0f", 1234.0); |
| if (strlen(testbuf) > 2 && testbuf[0] == '1' && testbuf[2] == '2') |
| badflag = '\0'; /* supported */ |
| else |
| badflag = '\''; /* not supported */ |
| } |
| |
| errors = 0; |
| |
| p = num_format.format; |
| |
| if (*p != '%') { |
| fprintf(stderr, "%s: number format specification must start with '%%'\n", |
| progname); |
| errors++; |
| } |
| else if (strrchr(num_format.format, '%') != num_format.format) { |
| fprintf(stderr, "%s: only one '%%' allowed in number format specification\n", |
| progname); |
| errors++; |
| p++; |
| } |
| else |
| p++; |
| |
| dotptr = strchr(num_format.format, '.'); |
| if (dotptr && strrchr(num_format.format, '.') != dotptr) { |
| fprintf(stderr, "%s: only one '.' allowed in number format specification\n", |
| progname); |
| errors++; |
| } |
| |
| /* skip over flags */ |
| while (*p && strchr(format_flags, *p)) { |
| if (*p == badflag) { /* only if digit-grouping flag (') not supported */ |
| fprintf(stderr, "%s: digit-grouping flag (') not supported\n", progname); |
| errors++; |
| } |
| p++; |
| } |
| |
| /* check for type length modifiers, which aren't allowed */ |
| if ((lptr = strstr(num_format.format, "hh")) |
| || (lptr = strstr(num_format.format, "ll"))) { |
| fprintf(stderr, "%s: type length modifier (%.2s) not supported\n", progname, lptr); |
| errors++; |
| } |
| else if ((lptr = strpbrk(num_format.format, "hjLltz"))) { |
| fprintf(stderr, "%s: type length modifier (%c) not supported\n", progname, lptr[0]); |
| errors++; |
| } |
| |
| /* check for other invalid characters */ |
| ndx = strspn(p, valid); |
| if (ndx < strlen(p)) { |
| fprintf(stderr, "%s: invalid character (%c) in width, precision, or type\n", |
| progname, p[ndx]); |
| errors++; |
| } |
| |
| if (errors) { /* results of any other checks are likely to be misleading */ |
| fprintf(stderr, "%s: invalid number format specification (%s)\n", |
| progname, num_format.format); |
| fprintf(stderr, "%s: valid specification is %%[flags][width][.precision]type\n", |
| progname); |
| return -1; |
| } |
| |
| /* get width and precision if specified; check precision */ |
| num_format.width = (int) strtol(p, &nonum, 10); |
| |
| if (*nonum == '.'){ |
| if (isdigit(nonum[1])) |
| num_format.precision = (int) strtol(nonum+1, &nonum, 10); |
| else { |
| num_format.precision = 0; |
| nonum++; |
| } |
| } |
| else /* precision not given--use printf() default */ |
| num_format.precision = 6; |
| |
| /* check for valid format type */ |
| if (emptystr(nonum)) { |
| fprintf(stderr, "%s: missing format type\n", progname); |
| errors++; |
| } |
| else { |
| if (strchr(format_types, *nonum)) { |
| if (nonum[1]) { |
| fprintf(stderr, "%s: invalid character(s) (%s) after format type\n", |
| progname, nonum + 1); |
| errors++; |
| } |
| else |
| num_format.type = *nonum; |
| } |
| else { |
| fprintf(stderr, |
| "%s: invalid format type (%c)--valid types are [%s]\n", |
| progname, *nonum, format_types); |
| errors++; |
| } |
| } |
| |
| if (num_format.precision == 0 && |
| (num_format.type == 'G' || num_format.type == 'g')) |
| num_format.precision = 1; |
| |
| if (errors) { |
| fprintf(stderr, "%s: invalid number format specification (%s)\n", |
| progname, num_format.format); |
| fprintf(stderr, "%s: valid specification is %%[flags][width][.precision]type\n", |
| progname); |
| return -1; |
| } |
| else |
| return 0; |
| } |
| |
| /* |
| round a number to the lesser of the displayed precision or the |
| remaining significant digits; indicate in hasnondigits if a number |
| will contain any character other than the digits 0-9 in the current |
| display format. |
| */ |
| |
| double |
| round_output(double value, int sigdigits, int *hasnondigits) |
| { |
| int buflen; |
| char *buf; |
| double rounded; |
| double mult_factor, rdigits; |
| int fmt_digits; /* decimal significant digits in format */ |
| |
| if (!isfinite(value)){ |
| if (hasnondigits) |
| *hasnondigits = 1; |
| return value; |
| } |
| |
| fmt_digits = num_format.precision; |
| switch (num_format.type) { |
| case 'A': |
| case 'a': |
| sigdigits = round(sigdigits * log2(10) / 4); |
| fmt_digits++; |
| break; |
| case 'E': |
| case 'e': |
| fmt_digits++; |
| break; |
| case 'F': |
| case 'f': |
| if (fabs(value) > 0) |
| fmt_digits += ceil(log10(fabs(value))); |
| break; |
| } |
| |
| if (sigdigits < fmt_digits) |
| rdigits = sigdigits; |
| else |
| rdigits = fmt_digits; |
| |
| /* place all significant digits to the right of the radix */ |
| if (value != 0) |
| rdigits -= ceil(log10(fabs(value))); |
| /* then handle like rounding to specified decimal places */ |
| mult_factor = pow(10.0, rdigits); |
| rounded = round(value * mult_factor) / mult_factor; |
| |
| /* allow for sign (1), radix (1), exponent (5), E or E formats (1), NUL */ |
| buflen = num_format.precision + 9; |
| |
| if (num_format.width > buflen) |
| buflen = num_format.width; |
| |
| if (strchr("Ff", num_format.type)) { |
| int len=num_format.precision+2; |
| if (fabs(value) > 1.0) |
| len += (int) floor(log10(fabs(value))) + 1; |
| if (len > buflen) |
| buflen = len; |
| } |
| |
| /* allocate space for thousands separators with digit-grouping (') flag */ |
| /* assume worst case--that all groups are two digits */ |
| if (strchr(num_format.format, '\'') && strchr("FfGg", num_format.type)) |
| buflen = buflen*3/2; |
| |
| buf = (char *) mymalloc(buflen, "(round_output)"); |
| sprintf(buf, num_format.format, value); |
| |
| if (hasnondigits){ |
| if (strspn(buf, "1234567890") != strlen(buf)) |
| *hasnondigits = 1; |
| else |
| *hasnondigits = 0; |
| } |
| |
| free(buf); |
| return rounded; |
| } |
| |
| /* |
| Determine significant digits in remainder relative to an original |
| value which is assumed to have full double precision. Returns |
| the number of binary or decimal digits depending on the value |
| of base, which must be 2 or 10. |
| */ |
| |
| int |
| getsigdigits(double original, double remainder, int base) |
| { |
| int sigdigits; |
| double maxdigits; |
| double (*logfunc)(double); |
| |
| if (base == 2) { |
| maxdigits = DBL_MANT_DIG; |
| logfunc = log2; |
| } |
| else { |
| maxdigits = DBL_MANT_DIG * log10(2.0); |
| logfunc = log10; |
| } |
| |
| if (original == 0) |
| return floor(maxdigits); |
| else if (remainder == 0) |
| return 0; |
| |
| sigdigits = floor(maxdigits - logfunc(fabs(original/remainder))); |
| |
| if (sigdigits < 0) |
| sigdigits = 0; |
| |
| return sigdigits; |
| } |
| |
| /* Rounds a double to the specified number of binary or decimal |
| digits. The base argument must be 2 or 10. */ |
| |
| double |
| round_digits(double value, int digits, int base) |
| { |
| double mult_factor; |
| double (*logfunc)(double); |
| |
| if (digits == 0) |
| return 0.0; |
| |
| if (base == 2) |
| logfunc = log2; |
| else |
| logfunc = log10; |
| |
| if (value != 0) |
| digits -= ceil(logfunc(fabs(value))); |
| |
| mult_factor = pow((double) base, digits); |
| |
| return round(value*mult_factor)/mult_factor; |
| } |
| |
| |
| /* Returns true if the value will display as equal to the reference |
| and if hasnondigits is non-null then return true if the displayed |
| output contains any character in "0123456789". */ |
| |
| int |
| displays_as(double reference, double value, int *hasnondigits) |
| { |
| int buflen; |
| char *buf; |
| double rounded; |
| |
| if (!isfinite(value)){ |
| if (hasnondigits) |
| *hasnondigits = 1; |
| return 0; |
| } |
| |
| /* allow for sign (1), radix (1), exponent (5), E or E formats (1), NUL */ |
| buflen = num_format.precision + 9; |
| |
| if (num_format.width > buflen) |
| buflen = num_format.width; |
| |
| if (strchr("Ff", num_format.type)) { |
| int len=num_format.precision+2; |
| if (fabs(value) > 1.0) |
| len += (int) floor(log10(fabs(value))) + 1; |
| if (len > buflen) |
| buflen = len; |
| } |
| |
| /* allocate space for thousands separators with digit-grouping (') flag */ |
| /* assume worst case--that all groups are two digits */ |
| if (strchr(num_format.format, '\'') && strchr("FfGg", num_format.type)) |
| buflen = buflen*3/2; |
| |
| buf = (char *) mymalloc(buflen, "(round_to_displayed)"); |
| sprintf(buf, num_format.format, value); |
| |
| if (hasnondigits){ |
| if (strspn(buf, "1234567890") != strlen(buf)) |
| *hasnondigits = 1; |
| else |
| *hasnondigits = 0; |
| } |
| rounded = strtod(buf, NULL); |
| free(buf); |
| |
| return rounded==reference; |
| } |
| |
| |
| |
| /* Print the unit in 'unitstr' along with any necessary punctuation. |
| The 'value' is the multiplier for the unit. If printnum is set |
| to PRINTNUM then this value is printed, or set it to NOPRINTNUM |
| to prevent the value from being printed. |
| */ |
| |
| #define PRINTNUM 1 |
| #define NOPRINTNUM 0 |
| |
| void |
| showunitname(double value, char *unitstr, int printnum) |
| { |
| int hasnondigits; /* flag to indicate nondigits in displayed value */ |
| int is_one; /* Does the value display as 1? */ |
| |
| is_one = displays_as(1, value, &hasnondigits); |
| |
| if (printnum && !(is_one && isdecimal(*unitstr))) |
| logprintf(num_format.format, value); |
| |
| if (strpbrk(unitstr, "+-")) /* show sums and differences of units */ |
| logprintf(" (%s)", unitstr); /* in parens */ |
| /* fractional unit 1|x and multiplier is all digits and not one-- */ |
| /* no space or asterisk or numerator (3|8 in instead of 3 * 1|8 in) */ |
| else if (printnum && !flags.showfactor |
| && startswith(unitstr,"1|") && isfract(unitstr) |
| && !is_one && !hasnondigits) |
| logputs(unitstr+1); |
| /* multiplier is unity and unit begins with a number--no space or */ |
| /* asterisk (multiplier was not shown, and the space was already output)*/ |
| else if (is_one && isdecimal(*unitstr)) |
| logputs(unitstr); |
| /* unitstr begins with a non-fraction number and multiplier was */ |
| /* shown--prefix a spaced asterisk */ |
| else if (isdecimal(unitstr[0])) |
| logprintf(" * %s", unitstr); |
| else |
| logprintf(" %s", unitstr); |
| } |
| |
| |
| /* Show the conversion factors or print the conformability error message */ |
| |
| int |
| showanswer(char *havestr,struct unittype *have, |
| char *wantstr,struct unittype *want) |
| { |
| struct unittype invhave; |
| int doingrec; /* reciprocal conversion? */ |
| char *right = NULL, *left = NULL; |
| |
| doingrec=0; |
| if (compareunits(have, want, ignore_dimless)) { |
| char **src,**dest; |
| |
| invhave.factor=1/have->factor; |
| for(src=have->numerator,dest=invhave.denominator;*src;src++,dest++) |
| *dest=*src; |
| *dest=0; |
| for(src=have->denominator,dest=invhave.numerator;*src;src++,dest++) |
| *dest=*src; |
| *dest=0; |
| if (flags.strictconvert || compareunits(&invhave, want, ignore_dimless)){ |
| showconformabilityerr(havestr, have, wantstr, want); |
| return -1; |
| } |
| if (flags.verbose>0) |
| logputchar('\t'); |
| logputs("reciprocal conversion\n"); |
| have=&invhave; |
| doingrec=1; |
| } |
| if (flags.verbose==2) { |
| if (!doingrec) |
| left=right=""; |
| else if (strchr(havestr,'/')) { |
| left="1 / ("; |
| right=")"; |
| } else { |
| left="1 / "; |
| right=""; |
| } |
| } |
| |
| /* Print the first line of output. */ |
| |
| if (flags.verbose==2) |
| logprintf("\t%s%s%s = ",left,havestr,right); |
| else if (flags.verbose==1) |
| logputs("\t* "); |
| if (flags.verbose==2) |
| showunitname(have->factor / want->factor, wantstr, PRINTNUM); |
| else |
| logprintf(num_format.format, have->factor / want->factor); |
| |
| /* Print the second line of output. */ |
| |
| if (!flags.oneline){ |
| if (flags.verbose==2) |
| logprintf("\n\t%s%s%s = (1 / ",left,havestr,right); |
| else if (flags.verbose==1) |
| logputs("\n\t/ "); |
| else |
| logputchar('\n'); |
| logprintf(num_format.format, want->factor / have->factor); |
| if (flags.verbose==2) { |
| logputchar(')'); |
| showunitname(0,wantstr, NOPRINTNUM); |
| } |
| } |
| logputchar('\n'); |
| return 0; |
| } |
| |
| |
| /* Checks that the function definition has a valid inverse |
| Prints a message to stdout if function has bad definition or |
| invalid inverse. |
| */ |
| |
| #define SIGN(x) ( (x) > 0.0 ? 1 : \ |
| ( (x) < 0.0 ? (-1) : \ |
| 0 )) |
| |
| void |
| checkfunc(struct func *infunc, int verbose) |
| { |
| struct unittype theunit, saveunit; |
| struct prefixlist *prefix; |
| int err, i; |
| double direction; |
| |
| if (infunc->skip_error_check){ |
| if (verbose) |
| printf("skipped function '%s'\n", infunc->name); |
| return; |
| } |
| if (verbose) |
| printf("doing function '%s'\n", infunc->name); |
| if ((prefix=plookup(infunc->name)) |
| && strlen(prefix->name)==strlen(infunc->name)) |
| printf("Warning: '%s' defined as prefix and function\n",infunc->name); |
| if (infunc->table){ |
| /* Check for valid unit for the table */ |
| if (parseunit(&theunit, infunc->tableunit, 0, 0) || |
| completereduce(&theunit)) |
| printf("Table '%s' has invalid units '%s'\n", |
| infunc->name, infunc->tableunit); |
| freeunit(&theunit); |
| |
| /* Check for monotonicity which is needed for unique inverses */ |
| if (infunc->tablelen<=1){ |
| printf("Table '%s' has only one data point\n", infunc->name); |
| return; |
| } |
| direction = SIGN(infunc->table[1].value - infunc->table[0].value); |
| for(i=2;i<infunc->tablelen;i++) |
| if (SIGN(infunc->table[i].value-infunc->table[i-1].value) != direction){ |
| printf("Table '%s' lacks unique inverse around entry " ERRNUMFMT "\n", |
| infunc->name, infunc->table[i].location); |
| return; |
| } |
| return; |
| } |
| if (infunc->forward.dimen){ |
| if (parseunit(&theunit, infunc->forward.dimen, 0, 0) || |
| completereduce(&theunit)){ |
| printf("Function '%s' has invalid units '%s'\n", |
| infunc->name, infunc->forward.dimen); |
| freeunit(&theunit); |
| return; |
| } |
| } else initializeunit(&theunit); |
| if (infunc->forward.domain_max && infunc->forward.domain_min) |
| theunit.factor *= |
| (*infunc->forward.domain_max+*infunc->forward.domain_min)/2; |
| else if (infunc->forward.domain_max) |
| theunit.factor = theunit.factor * *infunc->forward.domain_max - 1; |
| else if (infunc->forward.domain_min) |
| theunit.factor = theunit.factor * *infunc->forward.domain_min + 1; |
| else |
| theunit.factor *= 7; /* Arbitrary choice where we evaluate inverse */ |
| if (infunc->forward.dimen){ |
| unitcopy(&saveunit, &theunit); |
| err = evalfunc(&theunit, infunc, FUNCTION, ALLERR); |
| if (err) { |
| printf("Error in definition %s(%s) as '%s':\n", |
| infunc->name, infunc->forward.param, infunc->forward.def); |
| printf(" %s\n",errormsg[err]); |
| freeunit(&theunit); |
| freeunit(&saveunit); |
| return; |
| } |
| } else { |
| # define MAXPOWERTOCHECK 4 |
| struct unittype resultunit, arbunit; |
| char unittext[9]; |
| double factor; |
| int errors[MAXPOWERTOCHECK], errcount=0; |
| char *indent; |
| |
| strcpy(unittext,"(kg K)^ "); |
| factor = theunit.factor; |
| initializeunit(&saveunit); |
| initializeunit(&resultunit); |
| for(i=0;i<MAXPOWERTOCHECK;i++){ |
| lastchar(unittext) = '0'+i; |
| err = parseunit(&arbunit, unittext, 0, 0); |
| if (err) initializeunit(&arbunit); |
| arbunit.factor = factor; |
| unitcopy(&resultunit, &arbunit); |
| errors[i] = evalfunc(&resultunit, infunc, FUNCTION, ALLERR); |
| if (errors[i]) errcount++; |
| else { |
| freeunit(&saveunit); |
| freeunit(&theunit); |
| unitcopy(&saveunit, &arbunit); |
| unitcopy(&theunit, &resultunit); |
| } |
| freeunit(&resultunit); |
| freeunit(&arbunit); |
| } |
| if (!errors[0] && errcount==3) { |
| printf("Warning: function '%s(%s)' defined as '%s'\n", |
| infunc->name, infunc->forward.param, infunc->forward.def); |
| printf(" appears to require a dimensionless argument, 'units' keyword not given\n"); |
| indent = " "; |
| } |
| else if (errcount==MAXPOWERTOCHECK) { |
| printf("Error or missing 'units' keyword in definion %s(%s) as '%s'\n", |
| infunc->name, infunc->forward.param, infunc->forward.def); |
| indent=" "; |
| } |
| else if (errcount){ |
| printf("Warning: function '%s(%s)' defined as '%s'\n", |
| infunc->name, infunc->forward.param, infunc->forward.def); |
| printf(" failed for some test inputs:\n"); |
| indent = " "; |
| } |
| for(i=0;i<MAXPOWERTOCHECK;i++) |
| if (errors[i]) { |
| lastchar(unittext) = '0'+i; |
| printf("%s%s(",indent,infunc->name); |
| printf(num_format.format, factor); |
| printf("%s): %s\n", unittext, errormsg[errors[i]]); |
| } |
| } |
| if (completereduce(&theunit)){ |
| printf("Definition %s(%s) as '%s' is irreducible\n", |
| infunc->name, infunc->forward.param, infunc->forward.def); |
| freeunit(&theunit); |
| freeunit(&saveunit); |
| return; |
| } |
| if (!(infunc->inverse.def)){ |
| printf("Warning: no inverse for function '%s'\n", infunc->name); |
| freeunit(&theunit); |
| freeunit(&saveunit); |
| return; |
| } |
| err = evalfunc(&theunit, infunc, INVERSE, ALLERR); |
| if (err){ |
| printf("Error in inverse ~%s(%s) as '%s':\n", |
| infunc->name,infunc->inverse.param, infunc->inverse.def); |
| printf(" %s\n",errormsg[err]); |
| freeunit(&theunit); |
| freeunit(&saveunit); |
| return; |
| } |
| divunit(&theunit, &saveunit); |
| if (unit2num(&theunit) || fabs(theunit.factor-1)>1e-12) |
| printf("Inverse is not the inverse for function '%s'\n", infunc->name); |
| freeunit(&theunit); |
| } |
| |
| |
| struct namedef { |
| char *name; |
| char *def; |
| }; |
| |
| #define CONFORMABLE 1 |
| #define TEXTMATCH 2 |
| |
| void |
| addtolist(struct unittype *have, char *searchstring, char *rname, char *name, |
| char *def, struct namedef **list, int *listsize, |
| int *maxnamelen, int *count, int searchtype) |
| { |
| struct unittype want; |
| int len = 0; |
| int keepit = 0; |
| |
| if (!name) |
| return; |
| if (searchtype==CONFORMABLE){ |
| initializeunit(&want); |
| if (!parseunit(&want, name,0,0) && !completereduce(&want)) |
| keepit = !compareunits(have,&want,ignore_dimless); |
| } else if (searchtype == TEXTMATCH) { |
| keepit = (strstr(rname,searchstring) != NULL); |
| } |
| if (keepit){ |
| if (*count==*listsize){ |
| *listsize += 100; |
| *list = (struct namedef *) |
| realloc(*list,*listsize*sizeof(struct namedef)); |
| if (!*list){ |
| fprintf(stderr, "%s: memory allocation error (addtolist)\n", |
| progname); |
| exit(EXIT_FAILURE); |
| } |
| } |
| (*list)[*count].name = rname; |
| if (strchr(def, PRIMITIVECHAR)) |
| (*list)[*count].def = "<primitive unit>"; |
| else |
| (*list)[*count].def = def; |
| (*count)++; |
| len = strwidth(name); |
| if (len>*maxnamelen) |
| *maxnamelen = len; |
| } |
| if (searchtype == CONFORMABLE) |
| freeunit(&want); |
| } |
| |
| |
| int |
| compnd(const void *a, const void *b) |
| { |
| return strcmp(((struct namedef *)a)->name, ((struct namedef *)b)->name); |
| } |
| |
| |
| int |
| screensize() |
| { |
| int lines = 20; /* arbitrary but probably safe value */ |
| |
| #if defined (_WIN32) && defined (_MSC_VER) |
| CONSOLE_SCREEN_BUFFER_INFO csbi; |
| |
| if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) |
| lines = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; |
| #endif |
| |
| #ifdef HAVE_IOCTL |
| struct winsize ws; |
| int fd; |
| fd = open("/dev/tty", O_RDWR); |
| if (fd>=0 && ioctl(fd, TIOCGWINSZ, &ws)==0) |
| lines = ws.ws_row; |
| #endif |
| |
| return lines; |
| } |
| |
| /* |
| determines whether the output will fit on the screen, and opens a |
| pager if it won't |
| */ |
| FILE * |
| get_output_fp(int lines) |
| { |
| FILE *fp = NULL; |
| |
| if (isatty(fileno(stdout)) && screensize() < lines) { |
| if ((fp = popen(pager, "w")) == NULL) { |
| fprintf(stderr, "%s: can't run pager '%s--'", progname, pager); |
| perror((char*) NULL); |
| } |
| } |
| if (!fp) |
| fp = stdout; |
| |
| return fp; |
| } |
| |
| int |
| countlines(char *msg) |
| { |
| int nlines = 0; |
| char *p; |
| |
| for (p = msg; *p; p++) |
| if (*p == '\n') |
| nlines++; |
| |
| return nlines; |
| } |
| |
| /* |
| If have is non-NULL then search through all units and print the ones |
| which are conformable with have. Otherwise search through all the |
| units for ones whose names contain the second argument as a substring. |
| */ |
| |
| void |
| tryallunits(struct unittype *have, char *searchstring) |
| { |
| struct unitlist *uptr; |
| struct namedef *list; |
| int listsize, maxnamelen, count; |
| struct func *funcptr; |
| struct wantalias *aliasptr; |
| int i, j, searchtype; |
| FILE *outfile; |
| char *seploc, *firstunit; |
| |
| list = (struct namedef *) mymalloc( 100 * sizeof(struct namedef), |
| "(tryallunits)"); |
| listsize = 100; |
| maxnamelen = 0; |
| count = 0; |
| |
| if (have) |
| searchtype = CONFORMABLE; |
| else { |
| if (!searchstring) |
| searchstring=""; |
| searchtype = TEXTMATCH; |
| } |
| |
| for(i=0;i<HASHSIZE;i++) |
| for (uptr = utab[i]; uptr; uptr = uptr->next) |
| addtolist(have, searchstring, uptr->name, uptr->name, uptr->value, |
| &list, &listsize, &maxnamelen, &count, searchtype); |
| for(i=0;i<SIMPLEHASHSIZE;i++) |
| for(funcptr=ftab[i];funcptr;funcptr=funcptr->next){ |
| if (funcptr->table) |
| addtolist(have, searchstring, funcptr->name, funcptr->tableunit, |
| "<piecewise linear>", &list, &listsize, &maxnamelen, &count, |
| searchtype); |
| else |
| addtolist(have, searchstring, funcptr->name, funcptr->inverse.dimen, |
| "<nonlinear>", &list, &listsize, &maxnamelen, &count, |
| searchtype); |
| } |
| for(aliasptr=firstalias;aliasptr;aliasptr=aliasptr->next){ |
| firstunit = dupstr(aliasptr->definition);/* coverity[var_assigned] */ |
| seploc = strchr(firstunit,UNITSEPCHAR); /* Alias definitions allowed in */ |
| *seploc = 0; /* database contain UNITSEPCHAR */ |
| addtolist(have, searchstring, aliasptr->name, firstunit, |
| aliasptr->definition, &list, &listsize, &maxnamelen, &count, |
| searchtype); |
| free(firstunit); |
| } |
| |
| qsort(list, count, sizeof(struct namedef), compnd); |
| |
| if (count==0) |
| puts("No matching units found."); |
| #ifdef SIGPIPE |
| signal(SIGPIPE, SIG_IGN); |
| #endif |
| /* see if we need a pager */ |
| outfile = get_output_fp(count); |
| for(i=0;i<count;i++){ |
| fputs(list[i].name,outfile); |
| if (flags.verbose > 0 || flags.interactive) { |
| for(j=strwidth(list[i].name);j<=maxnamelen;j++) |
| putc(' ',outfile); |
| tightprint(outfile,list[i].def); |
| } |
| fputc('\n',outfile); |
| } |
| if (outfile != stdout) |
| pclose(outfile); |
| #ifdef SIGPIPE |
| signal(SIGPIPE, SIG_DFL); |
| #endif |
| } |
| |
| |
| /* If quiet is false then prompt user with the query. |
| |
| Fetch one line of input and return it in *buffer. |
| |
| The bufsize argument is a dummy argument if we are using readline. |
| The readline version frees buffer if it is non-null. The other |
| version keeps the same buffer and grows it as needed. |
| |
| If no data is read, then this function exits the program. |
| */ |
| |
| |
| void |
| getuser_noreadline(char **buffer, int *bufsize, const char *query) |
| { |
| #ifdef SUPPORT_UTF8 |
| int valid = 0; |
| while(!valid){ |
| fputs(query, stdout); |
| if (!fgetslong(buffer, bufsize, stdin,0)){ |
| if (!flags.quiet) |
| putchar('\n'); |
| exit(EXIT_SUCCESS); |
| } |
| valid = strwidth(*buffer)>=0; |
| if (!valid) |
| printf("Error: %s\n",invalid_utf8); |
| } |
| #else |
| fputs(query, stdout); |
| if (!fgetslong(buffer, bufsize, stdin,0)){ |
| if (!flags.quiet) |
| putchar('\n'); |
| exit(EXIT_SUCCESS); |
| } |
| #endif |
| } |
| |
| |
| #ifndef READLINE |
| # define getuser getuser_noreadline |
| #else |
| /* we DO have readline */ |
| void |
| getuser_readline(char **buffer, int *bufsize, const char *query) |
| { |
| #ifdef SUPPORT_UTF8 |
| int valid = 0; |
| while (!valid){ |
| if (*buffer) free(*buffer); |
| *buffer = readline(query); |
| if (*buffer) |
| replacectrlchars(*buffer); |
| if (!*buffer || strwidth(*buffer)>=0) |
| valid=1; |
| else |
| printf("Error: %s\n",invalid_utf8); |
| } |
| #else |
| if (*buffer) free(*buffer); |
| *buffer = readline(query); |
| if (*buffer) |
| replacectrlchars(*buffer); |
| #endif |
| if (nonempty(*buffer)) add_history(*buffer); |
| if (!*buffer){ |
| if (!flags.quiet) |
| putchar('\n'); |
| exit(EXIT_SUCCESS); |
| } |
| } |
| |
| |
| void |
| getuser(char **buffer, int *bufsize, const char *query) |
| { |
| if (flags.readline) |
| getuser_readline(buffer,bufsize,query); |
| else |
| getuser_noreadline(buffer,bufsize,query); |
| } |
| |
| |
| /* Unit name completion for readline. |
| |
| Complete function names or alias names or builtin functions. |
| |
| Complete to the end of a prefix or complete to the end of a unit. |
| If the text includes a full prefix plus part of a unit and if the |
| prefix is longer than one character then complete that compound. |
| Don't complete a prefix fragment into prefix plus anything. |
| */ |
| |
| #define CU_ALIAS 0 |
| #define CU_BUILTIN 1 |
| #define CU_FUNC 2 |
| #define CU_PREFIX 3 |
| #define CU_UNITS 4 |
| #define CU_DONE 5 |
| |
| char * |
| completeunits(char *text, int state) |
| { |
| static int uhash, fhash, phash, checktype; |
| static struct prefixlist *curprefix, *unitprefix; |
| static struct unitlist *curunit; |
| static struct func *curfunc; |
| static struct wantalias *curalias; |
| static char **curbuiltin; |
| char *output = 0; |
| |
| #ifndef NO_SUPPRESS_APPEND |
| rl_completion_suppress_append = 1; |
| #endif |
| |
| if (!state){ /* state == 0 means this is the first call, so initialize */ |
| checktype = 0; /* start at first type */ |
| fhash = uhash = phash = 0; |
| unitprefix=0; /* search for unit continuations starting with this prefix */ |
| curfunc=ftab[fhash]; |
| curunit=utab[uhash]; |
| curprefix=ptab[phash]; |
| curbuiltin = builtins; |
| curalias = firstalias; |
| } |
| while (checktype != CU_DONE){ |
| if (checktype == CU_ALIAS){ |
| while(curalias){ |
| if (startswith(curalias->name,text)) |
| output = dupstr(curalias->name); |
| curalias = curalias->next; |
| if (output) return output; |
| } |
| checktype++; |
| } |
| if (checktype == CU_BUILTIN){ |
| while(*curbuiltin){ |
| if (startswith(*curbuiltin,text)) |
| output = dupstr(*curbuiltin); |
| curbuiltin++; |
| if (output) return output; |
| } |
| checktype++; |
| } |
| while (checktype == CU_FUNC){ |
| while (!curfunc && fhash<SIMPLEHASHSIZE-1){ |
| fhash++; |
| curfunc = ftab[fhash]; |
| } |
| if (!curfunc) |
| checktype++; |
| else { |
| if (startswith(curfunc->name,text)) |
| output = dupstr(curfunc->name); |
| curfunc = curfunc->next; |
| if (output) return output; |
| } |
| } |
| while (checktype == CU_PREFIX){ |
| while (!curprefix && phash<SIMPLEHASHSIZE-1){ |
| phash++; |
| curprefix = ptab[phash]; |
| } |
| if (!curprefix) |
| checktype++; |
| else { |
| if (startswith(curprefix->name,text)) |
| output = dupstr(curprefix->name); |
| curprefix = curprefix->next; |
| if (output) return output; |
| } |
| } |
| while (checktype == CU_UNITS){ |
| while (!curunit && uhash<HASHSIZE-1){ |
| uhash++; |
| curunit = utab[uhash]; |
| } |
| /* If we're done with the units go through them again with */ |
| /* the largest possible prefix stripped off */ |
| if (!curunit && !unitprefix |
| && (unitprefix = plookup(text)) && strlen(unitprefix->name)>1){ |
| uhash = 0; |
| curunit = utab[uhash]; |
| } |
| if (!curunit) { |
| checktype++; |
| break; |
| } |
| if (unitprefix){ |
| if (startswith(curunit->name, text+unitprefix->len)){ |
| output = (char *)mymalloc(1+strlen(curunit->name)+unitprefix->len, |
| "(completeunits)"); |
| strcpy(output, unitprefix->name); |
| strcat(output, curunit->name); |
| } |
| } |
| else if (startswith(curunit->name,text)) |
| output = dupstr(curunit->name); |
| curunit=curunit->next; |
| if (output) |
| return output; |
| } |
| } |
| return 0; |
| } |
| |
| #endif /* READLINE */ |
| |
| |
| |
| /* see if current directory contains an executable file */ |
| int |
| checkcwd (char *file) |
| { |
| FILE *fp; |
| char *p; |
| |
| fp = openfile(file, "r"); |
| if (fp){ |
| fclose(fp); |
| return 1; |
| } |
| #ifdef _WIN32 |
| else if (!((p = strrchr(file, '.')) && isexe(p))) { |
| char *pathname; |
| |
| pathname = mymalloc(strlen(file) + strlen(EXE_EXT) + 1, |
| "(checkcwd)"); |
| strcpy(pathname, file); |
| strcat(pathname, EXE_EXT); |
| fp = openfile(pathname, "r"); |
| free(pathname); |
| if (fp) { |
| fclose(fp); |
| return 1; |
| } |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| /* |
| return the last component of a pathname, and remove a .exe extension |
| if one exists. |
| */ |
| |
| char * |
| getprogramname(char *path) |
| { |
| size_t proglen; |
| char *p; |
| |
| path = pathend(path); |
| |
| /* get rid of filename extensions in Windows */ |
| proglen = strlen(path); |
| |
| if ((p = strrchr(path, '.')) && isexe(p)) |
| proglen -= 4; |
| return dupnstr(path, proglen); |
| } |
| |
| /* |
| Find the directory that contains the invoked executable. |
| */ |
| |
| char * |
| getprogdir(char *progname, char **fullprogname) |
| { |
| char *progdir = NULL; |
| char *p; |
| |
| #if defined (_WIN32) && defined (_MSC_VER) |
| char buf[FILENAME_MAX + 1]; |
| |
| /* get the full pathname of the current executable and be done with it */ |
| /* TODO: is there way to do this with gcc? */ |
| |
| if (GetModuleFileName(NULL, buf, FILENAME_MAX + 1)) |
| progdir = dupstr(buf); |
| #endif |
| |
| /* If path name is absolute or includes more than one component use it */ |
| |
| if (!progdir && (isfullpath(progname) || hasdirsep(progname))) |
| progdir = dupstr(progname); |
| |
| |
| /* |
| command.com and cmd.exe under Windows always run a program that's in the |
| current directory whether or not the current directory is in PATH, so we need |
| to check the current directory. |
| |
| This could return a false positive if units is run from a Unix-like command |
| interpreter under Windows if the current directory is not in PATH but |
| contains 'units' or 'units.exe' |
| */ |
| #if defined (_WIN32) && !defined (_MSC_VER) |
| if (!progdir && checkcwd(progname)) |
| progdir = dupstr(progname); |
| #endif |
| |
| /* search PATH to find the executable */ |
| if (!progdir) { |
| char *env; |
| env = getenv("PATH"); |
| if (env) { |
| /* search PATH */ |
| char *direc, *direc_end, *pathname; |
| int len; |
| FILE *fp; |
| |
| pathname = mymalloc(strlen(env)+strlen(progname)+strlen(EXE_EXT)+2, |
| "(getprogdir)"); |
| direc = env; |
| while (direc) { |
| direc_end = strchr(direc,PATHSEP); |
| if (!direc_end) |
| len = strlen(direc); |
| else |
| len = direc_end-direc; |
| strncpy(pathname, direc, len); |
| if (len>0) |
| pathname[len++]=DIRSEP; |
| strcpy(pathname+len, progname); |
| fp = openfile(pathname, "r"); |
| if (fp){ |
| progdir = dupstr(pathname); |
| break; |
| } |
| #ifdef _WIN32 |
| /* |
| executable may or may not have '.exe' suffix, so we need to |
| look for both |
| */ |
| if (!((p = strrchr(pathname, '.')) && isexe(p))) { |
| strcat(pathname, EXE_EXT); |
| fp = openfile(pathname, "r"); |
| if (fp){ |
| progdir = dupstr(pathname); |
| break; |
| } |
| } |
| #endif |
| direc = direc_end; |
| if (direc) direc++; |
| } |
| free(pathname); |
| if (fp) |
| fclose(fp); |
| } |
| } |
| |
| if (!progdir) { |
| fprintf(stderr, "%s: cannot find program directory\n", progname); |
| exit(EXIT_FAILURE); |
| } |
| |
| *fullprogname = dupstr(progdir); /* used by printversion() */ |
| p = pathend(progdir); |
| *p = '\0'; |
| |
| return progdir; |
| } |
| |
| /* |
| find a possible data directory relative to a 'bin' directory that |
| contains the executable |
| */ |
| |
| char * |
| getdatadir() |
| { |
| int progdirlen; |
| char *p; |
| |
| if (isfullpath(DATADIR)) |
| return DATADIR; |
| if (!progdir || emptystr(DATADIR)) |
| return NULL; |
| progdirlen = strlen(progdir); |
| datadir = (char *) mymalloc(progdirlen + strlen(DATADIR) + 2, |
| "(getdatadir)"); |
| strcpy(datadir, progdir); |
| if (isdirsep(progdir[progdirlen - 1])) |
| datadir[progdirlen - 1] = '\0'; /* so pathend() can work */ |
| p = pathend(datadir); |
| if ((strlen(p) == 3) && (tolower(p[0]) == 'b') \ |
| && (tolower(p[1]) == 'i') && (tolower(p[2]) == 'n')) { |
| p = DATADIR; |
| while (*p == '.') /* ignore "./", "../" */ |
| p++; |
| if (isdirsep(*p)) |
| p++; |
| strcpy(pathend(datadir), p); |
| |
| return datadir; |
| } |
| else |
| return NULL; |
| } |
| |
| void |
| showfilecheck(int errnum, char *filename) |
| { |
| if (errnum==ENOENT) |
| printf(" Checking %s\n", filename); |
| else |
| printf(" Checking %s: %s\n", filename, strerror(errnum)); |
| } |
| |
| /* |
| On Windows, find the locale map |
| |
| If print is set to ERRMSG then display error message if the file is |
| not valid. If print is set to SHOWFILES then display files that are |
| checked (when the filename is not a fully specified path). If print |
| is set to NOERRMSG then do not display anything. |
| |
| Returns filename of valid local map file or NULL if no file was found. |
| */ |
| #ifdef _WIN32 |
| char * |
| findlocalemap(int print) |
| { |
| FILE *map = NULL; |
| char *filename = NULL; |
| char *file; |
| |
| /* |
| Try the environment variable UNITSLOCALEMAP, then the #defined |
| value LOCALEMAP, then the directory containing the units |
| executable, then the directory given by datadir, and finally |
| the directory containing the units data file. |
| */ |
| |
| /* check the environment variable UNITSLOCALEMAP */ |
| file = getenv("UNITSLOCALEMAP"); |
| if (nonempty(file)) { |
| map = openfile(file,"rt"); |
| if (!map) { |
| if (print == ERRMSG) { |
| fprintf(stderr, |
| "%s: cannot open locale map '%s'\n specified in UNITSLOCALEMAP environment variable. ", |
| progname, file); |
| perror((char *) NULL); |
| } |
| return NULL; |
| } |
| else |
| filename = dupstr(file); |
| } |
| |
| /* check the #defined value LOCALEMAP */ |
| if (!map) { |
| file = LOCALEMAP; |
| map = openfile(file,"rt"); |
| if (map) |
| filename = dupstr(file); |
| } |
| |
| if (!map && !progdir) { |
| if (print == ERRMSG) { |
| fprintf(stderr, |
| "%s: cannot find locale map--program directory not set\n", |
| progname); |
| exit(EXIT_FAILURE); |
| } |
| else |
| return NULL; |
| } |
| |
| /* check the directory with the units executable */ |
| if (!map) { |
| filename = (char *) mymalloc(strlen(progdir) + strlen(file) + 2, |
| "(findlocalemap)"); |
| strcpy(filename, progdir); |
| strcat(filename, file); |
| map = openfile(filename,"rt"); |
| if (print==SHOWFILES && !map) |
| showfilecheck(errno, filename); |
| } |
| |
| /* check data directory */ |
| if (!map && datadir) { |
| if (filename) |
| free(filename); |
| filename = (char *) mymalloc(strlen(datadir) + strlen(file) + 3, |
| "(findlocalemap)"); |
| strcpy(filename, datadir); |
| strcat(filename, DIRSEPSTR); |
| strcat(filename, file); |
| map = openfile(filename, "rt"); |
| if (print==SHOWFILES && !map) |
| showfilecheck(errno, filename); |
| } |
| |
| /* check the directory with the units data file */ |
| if (!map && unitsfiles[0]) { |
| char *lastfilename = NULL; |
| |
| if (filename) { |
| if (datadir) |
| lastfilename = dupstr(filename); |
| free(filename); |
| } |
| filename = (char *) mymalloc(strlen(unitsfiles[0]) + strlen(file) + 2, |
| "(findlocalemap)"); |
| strcpy(filename, unitsfiles[0]); |
| strcpy(pathend(filename), file); |
| |
| /* don't bother if we've just checked for it */ |
| if (lastfilename && strcmp(filename, lastfilename)) { |
| map = openfile(filename,"rt"); |
| if (print==SHOWFILES && !map) |
| showfilecheck(errno, filename); |
| } |
| } |
| |
| if (map) { |
| fclose(map); |
| return filename; |
| } |
| else { |
| if (filename) |
| free(filename); |
| return NULL; |
| } |
| } |
| #endif |
| |
| /* |
| Find the units database file. |
| |
| If print is set to ERRMSG then display error message if the file is |
| not valid. If print is set to SHOWFILES then display files that are |
| checked (when the filename is not a fully specified path). If print |
| is set to NOERRMSG then do not display anything. |
| |
| Returns filename of valid database file or NULL if no file |
| was found. |
| */ |
| |
| char * |
| findunitsfile(int print) |
| { |
| FILE *testfile=0; |
| char *file; |
| |
| file = getenv("UNITSFILE"); |
| if (nonempty(file)) { |
| testfile = openfile(file, "rt"); |
| if (!testfile) { |
| if (print==ERRMSG) { |
| fprintf(stderr, |
| "%s: cannot open units file '%s' in environment variable UNITSFILE. ", |
| progname, file); |
| perror((char *) NULL); |
| } |
| return NULL; |
| } |
| } |
| |
| if (!testfile && isfullpath(UNITSFILE)){ |
| file = UNITSFILE; |
| testfile = openfile(file, "rt"); |
| if (!testfile) { |
| if (print==ERRMSG) { |
| fprintf(stderr, |
| "%s: cannot open units data file '%s'. ", progname, UNITSFILE); |
| perror((char *) NULL); |
| } |
| return NULL; |
| } |
| } |
| |
| if (!testfile && !progdir) { |
| if (print==ERRMSG) { |
| fprintf(stderr, |
| "%s: cannot open units file '%s' and cannot find program directory.\n", progname, UNITSFILE); |
| perror((char *) NULL); |
| } |
| return NULL; |
| } |
| |
| if (!testfile) { |
| /* check the directory containing the units executable */ |
| file = (char *) mymalloc(strlen(progdir)+strlen(UNITSFILE)+1, |
| "(findunitsfile)"); |
| strcpy(file, progdir); |
| strcat(file, UNITSFILE); |
| testfile = openfile(file, "rt"); |
| if (print==SHOWFILES && !testfile) |
| showfilecheck(errno, file); |
| if (!testfile) |
| free(file); |
| } |
| |
| /* check data directory */ |
| if (!testfile && datadir) { |
| file = (char *) mymalloc(strlen(datadir) + strlen(UNITSFILE) + 2, |
| "(findunitsfile)"); |
| strcpy(file, datadir); |
| strcat(file, DIRSEPSTR); |
| strcat(file, UNITSFILE); |
| testfile = openfile(file, "rt"); |
| if (print==SHOWFILES && !testfile) |
| showfilecheck(errno, file); |
| if (!testfile) |
| free(file); |
| } |
| |
| if (!testfile) { |
| if (print==ERRMSG) |
| fprintf(stderr,"%s: cannot find units file '%s'\n", progname, UNITSFILE); |
| return NULL; |
| } |
| else { |
| fclose(testfile); |
| return file; |
| } |
| } |
| |
| /* |
| Find the user's home directory. Unlike *nix, Windows doesn't usually |
| set HOME, so we check several other places as well. |
| */ |
| char * |
| findhome(char **errmsg) |
| { |
| struct stat statbuf; |
| int allocated = 0; |
| char *homedir; |
| char notfound[] = "Specified home directory '%s' does not exist"; |
| char notadir[] = "Specified home directory '%s' is not a directory"; |
| |
| /* |
| Under UNIX just check HOME. Under Windows, if HOME is not set then |
| check HOMEDRIVE:HOMEPATH and finally USERPROFILE |
| */ |
| homedir = getenv("HOME"); |
| |
| #ifdef _WIN32 |
| if (!nonempty(homedir)) { |
| /* try a few other places */ |
| /* try HOMEDRIVE and HOMEPATH */ |
| char *homedrive, *homepath; |
| if ((homedrive = getenv("HOMEDRIVE")) && *homedrive && (homepath = getenv("HOMEPATH")) && *homepath) { |
| homedir = mymalloc(strlen(homedrive)+strlen(homepath)+1,"(personalfile)"); |
| allocated = 1; |
| strcpy(homedir, homedrive); |
| strcat(homedir, homepath); |
| } |
| else |
| /* finally, try USERPROFILE */ |
| homedir = getenv("USERPROFILE"); |
| } |
| #endif |
| |
| /* |
| If a home directory is specified, see if it exists and is a |
| directory. If not set error message text. |
| */ |
| |
| if (nonempty(homedir)) { |
| if (stat(homedir, &statbuf) != 0) { |
| *errmsg = malloc(strlen(notfound)+strlen(homedir)); |
| sprintf(*errmsg, notfound, homedir); |
| } |
| else if (!(statbuf.st_mode & S_IFDIR)) { |
| *errmsg = malloc(strlen(notadir)+strlen(homedir)); |
| sprintf(*errmsg, notadir, homedir); |
| } |
| if (!allocated) |
| homedir = dupstr(homedir); |
| return homedir; |
| } |
| else { |
| *errmsg = "no home directory"; |
| return NULL; |
| } |
| } |
| |
| /* |
| Find a personal file. First checks the specified environment variable |
| (envname) for the filename to use. If this is unset then search user's |
| home directory for basename. If there is no home directory, returns |
| NULL. Otherwise if the file exists then returns its name in newly allocated |
| space and sets *exists to 1. If the file does not exist then sets *exist to |
| zero and: |
| With checkonly == 0, prints error message and returns NULL |
| With checkonly != 0, returns filename (does not print error message) |
| */ |
| char * |
| personalfile(const char *envname, const char *basename, |
| int checkonly, int *exists) |
| { |
| FILE *testfile; |
| char *filename=NULL; |
| |
| *exists = 0; |
| |
| /* First check the specified environment variable for a file name */ |
| if (envname) |
| filename = getenv(envname); |
| if (nonempty(filename)){ |
| testfile = openfile(filename, "rt"); |
| if (testfile){ |
| fclose(testfile); |
| *exists = 1; |
| return filename; |
| } |
| if (checkonly) |
| return filename; |
| else { |
| fprintf(stderr, "%s: cannot open file '%s' specified in %s environment variable: ", |
| progname, filename, envname); |
| perror((char *) NULL); |
| return NULL; |
| } |
| } |
| /* Environment variable not set: look in home directory */ |
| else if (nonempty(homedir)) { |
| filename = mymalloc(strlen(homedir)+strlen(basename)+2, |
| "(personalfile)"); |
| strcpy(filename,homedir); |
| |
| if (strcmp(homedir, "/") && strcmp(homedir, "\\")) |
| strcat(filename,DIRSEPSTR); |
| strcat(filename,basename); |
| |
| testfile = openfile(filename, "rt"); |
| if (testfile){ |
| fclose(testfile); |
| *exists = 1; |
| return filename; |
| } |
| if (checkonly) |
| return filename; |
| else { |
| if (errno==EACCES || errno==EISDIR) { |
| fprintf(stderr,"%s: cannot open file '%s': ",progname,filename); |
| perror(NULL); |
| } |
| free(filename); |
| return NULL; |
| } |
| } |
| else |
| return NULL; |
| } |
| |
| |
| /* print usage message */ |
| |
| void |
| usage() |
| { |
| int nlines; |
| char *unitsfile; |
| char *msg = "\nUsage: %s [options] ['from-unit' 'to-unit']\n\n\ |
| Options:\n\ |
| -h, --help show this help and exit\n\ |
| -c, --check check that all units reduce to primitive units\n\ |
| --check-verbose like --check, but lists units as they are checked\n\ |
| --verbose-check so you can find units that cause endless loops\n\ |
| -d, --digits show output to specified number of digits (default: %d)\n\ |
| -e, --exponential exponential format output\n\ |
| -f, --file specify a units data file (-f '' loads default file)\n" |
| #ifdef READLINE |
| "\ |
| -H, --history specify readline history file (-H '' disables history)\n" |
| #endif |
| "\ |
| -L, --log specify a file to log conversions\n\ |
| -l, --locale specify a desired locale\n\ |
| -m, --minus make - into a subtraction operator (default)\n\ |
| --oldstar use old '*' precedence, higher than '/'\n\ |
| --newstar use new '*' precedence, equal to '/'\n\ |
| -n, --nolists disable conversion to unit lists\n\ |
| -S, --show-factor show non-unity factor before 1|x in multi-unit output\n\ |
| --conformable in non-interactive mode, show all conformable units\n\ |
| -o, --output-format specify printf numeric output format (default: %%.%d%c)\n\ |
| -p, --product make '-' into a product operator\n\ |
| -q, --quiet suppress prompting\n\ |
| --silent same as --quiet\n\ |
| -s, --strict suppress reciprocal unit conversion (e.g. Hz<->s)\n\ |
| -v, --verbose show slightly more verbose output\n\ |
| --compact suppress printing of tab, '*', and '/' character\n\ |
| -1, --one-line suppress the second line of output\n\ |
| -t, --terse terse output (--strict --compact --quiet --one-line)\n\ |
| -r, --round round last element of unit list output to an integer\n\ |
| -U, --unitsfile show units data filename and exit\n\ |
| -u, --units specify a CGS units system or natural units system:\n\ |
| gauss[ian],esu,emu,hlu,natural,natural-gauss,\n\ |
| hartree,planck,planck-red,si\n\ |
| -V, --version show version, data filenames (with -t: version only)\n\ |
| -I, --info show version, files, and program properties\n"; |
| FILE *fp = NULL; |
| |
| unitsfile = findunitsfile(NOERRMSG); |
| |
| nlines = countlines(msg); |
| /* see if we need a pager */ |
| fp = get_output_fp(nlines + 4); |
| |
| fprintf(fp, msg, progname, DEFAULTPRECISION, DEFAULTPRECISION, DEFAULTTYPE); |
| if (!unitsfile) |
| fprintf(fp, "Units data file '%s' not found.\n\n", UNITSFILE); |
| else |
| fprintf(fp, "\nTo learn about the available units look in '%s'\n\n", unitsfile); |
| fputs("Report bugs to adrianm@gnu.org.\n\n", fp); |
| |
| if (fp != stdout) |
| pclose(fp); |
| } |
| |
| /* Print message about how to get help */ |
| |
| void |
| helpmsg() |
| { |
| fprintf(stderr,"\nTry '%s --help' for more information.\n",progname); |
| exit(EXIT_FAILURE); |
| } |
| |
| /* show units version, and optionally, additional information */ |
| void |
| printversion() |
| { |
| int exists; |
| char *u_unitsfile = NULL; /* units data file specified in UNITSFILE */ |
| char *m_unitsfile; /* personal units data file from HOME_UNITS_ENV */ |
| char *p_unitsfile; /* personal units data file */ |
| FILE *fp, *histfile; |
| #ifdef _WIN32 |
| char *localemap; |
| #endif |
| |
| if (flags.verbose == 0) { |
| printf("GNU Units version %s\n", VERSION); |
| return; |
| } |
| |
| printf("GNU Units version %s\n%s, %s, locale %s\n", |
| VERSION, RVERSTR,UTF8VERSTR,mylocale); |
| #if defined (_WIN32) && defined (HAVE_MKS_TOOLKIT) |
| puts("With MKS Toolkit"); |
| #endif |
| |
| if (flags.verbose == 2) { |
| if (!fullprogname) |
| getprogdir(progname, &fullprogname); |
| if (fullprogname) |
| printf("\n%s program is %s\n", progname, fullprogname); |
| } |
| |
| /* units data file */ |
| |
| putchar('\n'); |
| if (isfullpath(UNITSFILE)) |
| printf("Default units data file is '%s'\n", UNITSFILE); |
| else |
| printf("Default units data file is '%s';\n %s will search for this file\n", |
| UNITSFILE, progname); |
| if (flags.verbose < 2) |
| printf("Default personal units file: %s\n", homeunitsfile); |
| |
| if (flags.verbose == 2){ |
| u_unitsfile = getenv("UNITSFILE"); |
| if (u_unitsfile) |
| printf("Environment variable UNITSFILE set to '%s'\n", u_unitsfile); |
| else |
| puts("Environment variable UNITSFILE not set"); |
| |
| unitsfiles[0] = findunitsfile(SHOWFILES); |
| |
| if (unitsfiles[0]) { |
| /* We searched for the file in program and data dirs */ |
| if (!isfullpath(UNITSFILE) && !nonempty(u_unitsfile)) |
| printf("Found data file '%s'\n", unitsfiles[0]); |
| else |
| printf("Units data file is '%s'\n", unitsfiles[0]); |
| } |
| else { |
| if (errno && (nonempty(u_unitsfile) || isfullpath(UNITSFILE))) |
| printf("*** Units data file invalid: %s ***\n",strerror(errno)); |
| else |
| puts("*** Units data file not found ***"); |
| } |
| if (homedir_error) |
| printf("\n%s\n", homedir_error); |
| else |
| printf("\nHome directory is '%s'\n", homedir); |
| } |
| |
| /* personal units data file: environment */ |
| if (flags.verbose == 2){ |
| m_unitsfile = getenv(HOME_UNITS_ENV); |
| putchar('\n'); |
| if (m_unitsfile) { |
| printf("Environment variable %s set to '%s'\n", |
| HOME_UNITS_ENV,m_unitsfile); |
| } |
| else |
| printf("Environment variable %s not set\n", HOME_UNITS_ENV); |
| |
| p_unitsfile = personalfile(HOME_UNITS_ENV, homeunitsfile, 1, &exists); |
| if (p_unitsfile) { |
| printf("Personal units data file is '%s'\n", p_unitsfile); |
| if (!exists){ |
| if (homedir_error && !nonempty(m_unitsfile)) |
| printf(" (File invalid: %s)\n", homedir_error); |
| else if (errno==ENOENT && !nonempty(m_unitsfile)) |
| puts(" (File does not exist)"); |
| else |
| printf(" (File invalid: %s)\n",strerror(errno)); |
| } |
| } |
| else |
| puts("Personal units data file not found: no home directory"); |
| } |
| #ifdef READLINE |
| if (flags.verbose == 2) { |
| historyfile = personalfile(NULL,HISTORY_FILE,1,&exists); |
| if (historyfile){ |
| printf("\nDefault readline history file is '%s'\n", historyfile); |
| histfile = openfile(historyfile,"r+"); |
| if (!histfile) |
| printf(" (File invalid: %s)\n", |
| homedir_error ? homedir_error : strerror(errno)); |
| else |
| fclose(histfile); |
| } |
| else |
| puts("\nReadline history file unusable: no home directory"); |
| } |
| #endif |
| |
| #ifdef _WIN32 |
| /* locale map */ |
| if (flags.verbose == 2) { |
| putchar('\n'); |
| localemap = getenv("UNITSLOCALEMAP"); |
| if (localemap) |
| printf("Environment variable UNITSLOCALEMAP set to '%s'\n", localemap); |
| else |
| puts("Environment variable UNITSLOCALEMAP not set"); |
| |
| if (isfullpath(LOCALEMAP)) |
| printf("Default locale map is '%s'\n", LOCALEMAP); |
| else |
| printf("Default locale map is '%s';\n %s will search for this file\n", |
| LOCALEMAP, progname); |
| |
| localemap = findlocalemap(SHOWFILES); |
| if (localemap && !isfullpath(LOCALEMAP)) |
| printf("Found locale map '%s'\n", localemap); |
| else if (localemap) |
| printf("Locale map is '%s'\n", localemap); |
| else |
| puts("*** Locale map not found ***"); |
| } |
| #endif |
| |
| printf("\n%s\n\n", LICENSE); |
| } |
| |
| void |
| showunitsfile() |
| { |
| char *unitsfile; |
| unitsfile = findunitsfile(NOERRMSG); |
| if (unitsfile) |
| printf("%s\n", unitsfile); |
| else |
| puts("Units data file not found"); |
| } |
| |
| |
| char *shortoptions = "VIUu:vqechSstf:o:d:mnpr1l:L:" |
| #ifdef READLINE |
| "H:" |
| #endif |
| ; |
| |
| struct option longoptions[] = { |
| {"check", no_argument, &flags.unitcheck, 1}, |
| {"check-verbose", no_argument, &flags.unitcheck, 2}, |
| {"compact", no_argument, &flags.verbose, 0}, |
| {"digits", required_argument, 0, 'd'}, |
| {"exponential", no_argument, 0, 'e'}, |
| {"file", required_argument, 0, 'f'}, |
| {"help", no_argument, 0, 'h'}, |
| #ifdef READLINE |
| {"history", required_argument, 0, 'H'}, |
| #endif |
| {"info", no_argument, 0, 'I'}, |
| {"locale", required_argument, 0, 'l'}, |
| {"log", required_argument, 0, 'L'}, |
| {"minus", no_argument, &parserflags.minusminus, 1}, |
| {"newstar", no_argument, &parserflags.oldstar, 0}, |
| {"nolists", no_argument, 0, 'n'}, |
| {"oldstar", no_argument, &parserflags.oldstar, 1}, |
| {"one-line", no_argument, &flags.oneline, 1}, |
| {"output-format", required_argument, 0, 'o'}, |
| {"product", no_argument, &parserflags.minusminus, 0}, |
| {"quiet", no_argument, &flags.quiet, 1}, |
| {"round",no_argument, 0, 'r'}, |
| {"show-factor", no_argument, 0, 'S'}, |
| {"conformable", no_argument, &flags.showconformable, 1 }, |
| {"silent", no_argument, &flags.quiet, 1}, |
| {"strict",no_argument,&flags.strictconvert, 1}, |
| {"terse",no_argument, 0, 't'}, |
| {"unitsfile", no_argument, 0, 'U'}, |
| {"units", required_argument, 0, 'u'}, |
| {"verbose", no_argument, &flags.verbose, 2}, |
| {"verbose-check", no_argument, &flags.unitcheck, 2}, |
| {"version", no_argument, 0, 'V'}, |
| {0,0,0,0} }; |
| |
| /* Process the args. Returns 1 if interactive mode is desired, and 0 |
| for command line operation. If units appear on the command line |
| they are returned in the from and to parameters. */ |
| |
| int |
| processargs(int argc, char **argv, char **from, char **to) |
| { |
| optarg = 0; |
| optind = 0; |
| int optchar, optindex; |
| int ind; |
| int doprintversion=0; |
| char *unitsys=0, *temp; |
| |
| while ( -1 != |
| (optchar = |
| getopt_long(argc, argv,shortoptions,longoptions, &optindex ))) { |
| switch (optchar) { |
| case 'm': |
| parserflags.minusminus = 1; |
| break; |
| case 'p': |
| parserflags.minusminus = 0; |
| break; |
| case 't': |
| flags.oneline = 1; |
| flags.quiet = 1; |
| flags.strictconvert = 1; |
| flags.verbose = 0; |
| break; |
| |
| /* numeric output format */ |
| case 'd': |
| if (checksigdigits(optarg) < 0) |
| exit(EXIT_FAILURE); |
| else /* ignore anything given with 'o' option */ |
| num_format.format = NULL; |
| break; |
| case 'e': /* ignore anything given with 'o' option */ |
| num_format.format = NULL; |
| num_format.type = 'e'; |
| break; |
| case 'o': |
| num_format.format = optarg; |
| break; |
| |
| case 'c': |
| flags.unitcheck = 1; |
| break; |
| case 'f': |
| for(ind=0;unitsfiles[ind];ind++); |
| if (ind==MAXFILES){ |
| fprintf(stderr, "At most %d -f specifications are allowed\n", |
| MAXFILES); |
| exit(EXIT_FAILURE); |
| } |
| if (optarg && *optarg) |
| unitsfiles[ind] = optarg; |
| else { |
| unitsfiles[ind] = findunitsfile(ERRMSG); |
| if (!unitsfiles[ind]) |
| exit(EXIT_FAILURE); |
| } |
| unitsfiles[ind+1] = 0; |
| break; |
| case 'L': |
| logfilename = optarg; |
| break; |
| case 'l': |
| mylocale = optarg; |
| break; |
| case 'n': |
| flags.unitlists = 0; |
| break; |
| case 'q': |
| flags.quiet = 1; |
| break; |
| case 'r': |
| flags.round = 1; |
| break; |
| case 'S': |
| flags.showfactor = 1; |
| break; |
| case 's': |
| flags.strictconvert = 1; |
| break; |
| case 'v': |
| flags.verbose = 2; |
| break; |
| case '1': |
| flags.oneline = 1; |
| break; |
| case 'I': |
| flags.verbose = 2; /* fall through */ |
| case 'V': |
| doprintversion = 1; |
| break; |
| case 'U': |
| showunitsfile(); |
| exit(EXIT_SUCCESS); |
| break; |
| case 'u': |
| unitsys = optarg; |
| for(ind=0;unitsys[ind];ind++) |
| unitsys[ind] = tolower(unitsys[ind]); |
| break; |
| case 'h': |
| usage(); |
| exit(EXIT_SUCCESS); |
| #ifdef READLINE |
| case 'H': |
| if (emptystr(optarg)) |
| historyfile=NULL; |
| else |
| historyfile = optarg; |
| break; |
| #endif |
| case 0: break; /* This is reached if a long option is |
| processed with no return value set. */ |
| case '?': /* Invalid option or missing argument returns '?' */ |
| default: |
| helpmsg(); /* helpmsg() exits with error */ |
| } |
| } |
| |
| temp = strchr(mylocale,'.'); |
| if (temp) |
| *temp = '\0'; |
| |
| if (doprintversion){ |
| printversion(); |
| exit(EXIT_SUCCESS); |
| } |
| |
| /* command-line option overwrites environment */ |
| if (unitsys) |
| setenv("UNITS_SYSTEM", unitsys, 1); |
| |
| if (flags.unitcheck) { |
| if (optind != argc){ |
| fprintf(stderr, |
| "Too many arguments (arguments are not allowed with -c).\n"); |
| helpmsg(); /* helpmsg() exits with error */ |
| } |
| } else { |
| if (optind == argc - 2) { |
| if (flags.showconformable) { |
| fprintf(stderr,"Too many arguments (only one unit expression allowed with '--conformable').\n"); |
| helpmsg(); /* helpmsg() exits with error */ |
| } |
| flags.quiet=1; |
| *from = argv[optind]; |
| *to = dupstr(argv[optind+1]); /* This string may get rewritten later */ |
| return 0; /* and we might call free() on it */ |
| } |
| |
| if (optind == argc - 1) { |
| flags.quiet=1; |
| *from = argv[optind]; |
| *to=0; |
| return 0; |
| } |
| if (optind < argc - 2) { |
| fprintf(stderr,"Too many arguments (maybe you need quotes).\n"); |
| helpmsg(); /* helpmsg() exits with error */ |
| } |
| } |
| |
| return 1; |
| } |
| |
| /* |
| Show a pointer under the input to indicate a problem. |
| Prints 'position' spaces and then the pointer. |
| If 'position' is negative, nothing is printed. |
| */ |
| |
| void |
| showpointer(int position) |
| { |
| if (position >= 0){ |
| while (position--) putchar(' '); |
| puts(POINTER); |
| } |
| } /* end showpointer */ |
| |
| |
| /* |
| Process the string 'unitstr' as a unit, placing the processed data |
| in the unit structure 'theunit'. Returns 0 on success and 1 on |
| failure. If an error occurs an error message is printed to stdout. |
| A pointer ('^') will be printed if an error is detected, and promptlen |
| should be set to the printing width of the prompt string, or set |
| it to NOPOINT to supress printing of the pointer. |
| */ |
| |
| |
| int |
| processunit(struct unittype *theunit, char *unitstr, int promptlen) |
| { |
| char *errmsg; |
| int errloc,err; |
| char savechar; |
| |
| if (flags.unitlists && strchr(unitstr, UNITSEPCHAR)){ |
| puts("Unit list not allowed"); |
| return 1; |
| } |
| if ((err=parseunit(theunit, unitstr, &errmsg, &errloc))){ |
| if (promptlen >= 0){ |
| if (err!=E_UNKNOWNUNIT || !irreducible){ |
| if (errloc>0) { |
| savechar = unitstr[errloc]; |
| unitstr[errloc] = 0; |
| showpointer(promptlen+strwidth(unitstr)-1); |
| unitstr[errloc] = savechar; |
| } |
| else showpointer(promptlen); |
| } |
| } |
| else |
| printf("Error in '%s': ", unitstr); |
| fputs(errmsg,stdout); |
| if (err==E_UNKNOWNUNIT && irreducible) |
| printf(" '%s'", irreducible); |
| putchar('\n'); |
| return 1; |
| } |
| if ((err=completereduce(theunit))){ |
| fputs(errormsg[err],stdout); |
| if (err==E_UNKNOWNUNIT) |
| printf(" '%s'", irreducible); |
| putchar('\n'); |
| return 1; |
| } |
| return 0; |
| } |
| |
| |
| /* Checks for a new unit defined on the prompt with the form _<NAME> = <DEF> */ |
| |
| int |
| definevariable(char *def, int promptlen) |
| { |
| int dummy, err; |
| struct unittype unit; |
| |
| char *value = strchr(def,'='); |
| if (!value) |
| return 0; |
| *value++=0; |
| if (processunit(&unit, value, promptlen + (value-def))) |
| return 1; |
| removespaces(def); |
| removespaces(value); |
| err = *def!='_' || newunit(def,value, &dummy, 0, 0, NULL, 1, 1); |
| if (err) |
| printf("Invalid variable name: %s\n", def); |
| return 1; |
| } |
| |
| |
| |
| |
| /* |
| Checks the input parameter unitstr (a list of units separated by |
| UNITSEPCHAR) for errors. All units must be parseable and |
| conformable to each other. Returns 0 on success and 1 on failure. |
| |
| If an error is found then print an error message on stdout. A |
| pointer ('^') will be printed to mark the error. The promptlen |
| parameter should be set to the printing width of the prompt string |
| so that the pointer is correctly aligned. |
| |
| To suppress the printing of the pointer set promptlen to NOPOINT. |
| To suppress printing of error messages entirely set promptlen to |
| NOERRMSG. |
| */ |
| |
| |
| int |
| checkunitlist(char *unitstr, int promptlen) |
| { |
| struct unittype unit[2], one; |
| char *firstunitstr,*nextunitstr; |
| int unitidx = 0; |
| |
| int printerror = promptlen != NOERRMSG; |
| |
| initializeunit(&one); |
| |
| firstunitstr = unitstr; |
| |
| initializeunit(unit); |
| initializeunit(unit+1); |
| |
| while (unitstr) { |
| if ((nextunitstr = strchr(unitstr, UNITSEPCHAR)) != 0) |
| *nextunitstr = '\0'; |
| |
| if (!unitstr[strspn(unitstr, " ")]) { /* unitstr is blank */ |
| if (!nextunitstr) { /* terminal UNITSEPCHAR indicates repetition */ |
| freeunit(unit); /* of last unit and is permitted */ |
| return 0; |
| } |
| else { /* internal blank units are not allowed */ |
| if (printerror){ |
| showpointer(promptlen); |
| puts("Error: blank unit not allowed"); |
| } |
| freeunit(unit); |
| return 1; |
| } |
| } |
| |
| /* processunit() prints error messages; avoid it to supress them */ |
| |
| if ((printerror && processunit(unit+unitidx,unitstr,promptlen)) || |
| (!printerror && |
| (parseunit(unit+unitidx, unitstr,0,0) |
| || completereduce(unit+unitidx) |
| || compareunits(unit+unitidx,&one, ignore_primitive)))){ |
| if (printerror) |
| printf("Error in unit list entry: %s\n",unitstr); |
| freeunit(unit); |
| freeunit(unit+1); |
| return 1; |
| } |
| |
| |
| if (unitidx == 0) |
| unitidx = 1; |
| else { |
| if (compareunits(unit, unit+1, ignore_dimless)){ |
| if (printerror){ |
| int wasverbose = flags.verbose; |
| FILE *savelog = logfile; |
| logfile=0; |
| flags.verbose = 2; /* always use verbose form to be unambiguous */ |
| /* coverity[returned_null] */ |
| *(strchr(firstunitstr, UNITSEPCHAR)) = '\0'; |
| removespaces(firstunitstr); |
| removespaces(unitstr); |
| showpointer(promptlen); |
| showconformabilityerr(firstunitstr, unit, unitstr, unit+1); |
| flags.verbose = wasverbose; |
| logfile = savelog; |
| } |
| freeunit(unit); |
| freeunit(unit+1); |
| return 1; |
| } |
| freeunit(unit+1); |
| } |
| |
| if (nextunitstr) { |
| if (promptlen >= 0) promptlen += strwidth(unitstr)+1; |
| *(nextunitstr++) = UNITSEPCHAR; |
| } |
| unitstr = nextunitstr; |
| } |
| |
| freeunit(unit); |
| |
| return 0; |
| } /* end checkunitlist */ |
| |
| |
| /* |
| Call either processunit or checkunitlist, depending on whether the |
| string 'unitstr' contains a separator character. Returns 0 on |
| success and 1 on failure. If an error occurs an error message is |
| printed to stdout. |
| |
| A pointer will be printed if an error is detected, and promptlen |
| should be set to the printing width of the prompt string, or set |
| it to NOPOINT to supress printing of the pointer. |
| */ |
| |
| int |
| processwant(struct unittype *theunit, char *unitstr, int promptlen) |
| { |
| if (flags.unitlists && strchr(unitstr, UNITSEPCHAR)) |
| return checkunitlist(unitstr, promptlen); |
| else |
| return processunit(theunit, unitstr, promptlen); |
| } |
| |
| |
| void |
| checkallaliases(int verbose) |
| { |
| struct wantalias *aliasptr; |
| |
| for(aliasptr = firstalias; aliasptr; aliasptr=aliasptr->next){ |
| if (verbose) |
| printf("doing unit list '%s'\n", aliasptr->name); |
| if (checkunitlist(aliasptr->definition,NOERRMSG)) |
| printf("Unit list '%s' contains errors\n", aliasptr->name); |
| if (ulookup(aliasptr->name)) |
| printf("Unit list '%s' hides a unit definition.\n", aliasptr->name); |
| if (fnlookup(aliasptr->name)) |
| printf("Unit list '%s' hides a function definition.\n", aliasptr->name); |
| } |
| } |
| |
| |
| |
| /* |
| Check that all units and prefixes are reducible to primitive units and that |
| function definitions are valid and have correct inverses. A message is |
| printed for every unit that does not reduce to primitive units. |
| |
| */ |
| |
| |
| void |
| checkunits(int verbosecheck) |
| { |
| struct unittype have,second,one; |
| struct unitlist *uptr; |
| struct prefixlist *pptr; |
| struct func *funcptr; |
| char *prefixbuf, *testunit; |
| int i, reduce_err; |
| char *err; |
| |
| initializeunit(&one); |
| |
| /* Check all functions for valid definition and correct inverse */ |
| |
| for(i=0;i<SIMPLEHASHSIZE;i++) |
| for(funcptr=ftab[i];funcptr;funcptr=funcptr->next) |
| checkfunc(funcptr, verbosecheck); |
| |
| checkallaliases(verbosecheck); |
| |
| /* Now check all units for validity */ |
| |
| for(i=0;i<HASHSIZE;i++) |
| for (uptr = utab[i]; uptr; uptr = uptr->next){ |
| if (verbosecheck) |
| printf("doing '%s'\n",uptr->name); |
| if (strchr(uptr->value, PRIMITIVECHAR)) |
| continue; |
| else if (fnlookup(uptr->name)) |
| printf("Unit '%s' hidden by function '%s'\n", uptr->name, uptr->name); |
| else if (parseunit(&have, uptr->value,&err,0)) |
| printf("'%s' defined as '%s': %s\n",uptr->name, uptr->value,err); |
| else if ((reduce_err=completereduce(&have))){ |
| printf("'%s' defined as '%s' irreducible: %s",uptr->name, uptr->value,errormsg[reduce_err]); |
| if (reduce_err==E_UNKNOWNUNIT && irreducible) |
| printf(" '%s'", irreducible); |
| putchar('\n'); |
| } |
| else { |
| parserflags.minusminus = !parserflags.minusminus; |
| /* coverity[check_return] */ |
| parseunit(&second, uptr->name, 0, 0); /* coverity[check_return] */ |
| completereduce(&second); /* Can't fail because it worked above */ |
| if (compareunits(&have, &second, ignore_nothing)){ |
| printf("'%s': replace '-' with '+-' for subtraction or '*' to multiply\n", uptr->name); |
| } |
| freeunit(&second); |
| parserflags.minusminus=!parserflags.minusminus; |
| } |
| |
| freeunit(&have); |
| } |
| |
| /* Check prefixes */ |
| |
| testunit="meter"; |
| for(i=0;i<SIMPLEHASHSIZE;i++) |
| for(pptr = ptab[i]; pptr; pptr = pptr->next){ |
| if (verbosecheck) |
| printf("doing '%s-'\n",pptr->name); |
| prefixbuf = mymalloc(strlen(pptr->name) + strlen(testunit) + 1, |
| "(checkunits)"); |
| strcpy(prefixbuf,pptr->name); |
| strcat(prefixbuf,testunit); |
| if (parseunit(&have, prefixbuf,0,0) || completereduce(&have) || |
| compareunits(&have,&one,ignore_primitive)) |
| printf("'%s-' defined as '%s' irreducible\n",pptr->name, pptr->value); |
| else { |
| int plevel; /* check for bad '/' character in prefix */ |
| char *ch; |
| plevel = 0; |
| for(ch=pptr->value;*ch;ch++){ |
| if (*ch==')') plevel--; |
| else if (*ch=='(') plevel++; |
| else if (plevel==0 && *ch=='/'){ |
| printf( |
| "'%s-' defined as '%s' contains a bad '/'. (Add parentheses.)\n", |
| pptr->name, pptr->value); |
| break; |
| } |
| } |
| } |
| freeunit(&have); |
| free(prefixbuf); |
| } |
| } |
| |
| |
| /* |
| Converts the input value 'havestr' (which is already parsed into |
| the unit structure 'have') into a sum of the UNITSEPCHAR-separated |
| units listed in 'wantstr'. You must call checkunitlist first to |
| ensure 'wantstr' is error-free. Prints the results (or an error message) |
| on stdout. Returns 0 on success and 1 on failure. |
| */ |
| |
| int |
| showunitlist(char *havestr, struct unittype *have, char *wantstr) |
| { |
| struct unittype want, lastwant; |
| char *lastunitstr, *nextunitstr, *lastwantstr=0; |
| double remainder; /* portion of have->factor remaining */ |
| double round_dir; /* direction of rounding */ |
| double value; /* value (rounded to integer with 'r' option) */ |
| int firstunit = 1; /* first unit in a multi-unit string */ |
| int value_shown = 0; /* has a value been shown? */ |
| int sigdigits; |
| char val_sign; |
| |
| initializeunit(&want); |
| remainder = fabs(have->factor); |
| val_sign = have->factor < 0 ? '-' : '+'; |
| lastunitstr = 0; |
| nextunitstr = 0; |
| round_dir = 0; |
| |
| if (flags.round) { |
| /* disable unit repetition with terminal UNITSEPCHAR when rounding */ |
| if (lastchar(wantstr) == UNITSEPCHAR) |
| lastchar(wantstr) = 0; |
| if ((lastwantstr = strrchr(wantstr, UNITSEPCHAR))) |
| lastwantstr++; |
| } |
| |
| while (wantstr) { |
| if ((nextunitstr = strchr(wantstr, UNITSEPCHAR))) |
| *(nextunitstr++) = '\0'; |
| removespaces(wantstr); |
| |
| /* |
| if wantstr ends in UNITSEPCHAR, repeat last unit--to give integer |
| and fractional parts (3 oz + 0.371241 oz rather than 3.371241 oz) |
| */ |
| if (emptystr(wantstr)) /* coverity[alias_transfer] */ |
| wantstr = lastunitstr; |
| |
| if (processunit(&want, wantstr, NOPOINT)) { |
| freeunit(&want); |
| return 1; |
| } |
| |
| if (firstunit){ |
| /* checkunitlist() ensures conformability within 'wantstr', |
| so we just need to check the first unit to see if it conforms |
| to 'have' */ |
| if (compareunits(have, &want, ignore_dimless)) { |
| showconformabilityerr(havestr, have, wantstr, &want); |
| freeunit(&want); |
| return 1; |
| } |
| |
| /* round to nearest integral multiple of last unit */ |
| if (flags.round) { |
| value = remainder; |
| if (nonempty(lastwantstr)) { /* more than one unit */ |
| removespaces(lastwantstr); |
| initializeunit(&lastwant); |
| if (processunit(&lastwant, lastwantstr, NOPOINT)) { |
| freeunit(&lastwant); |
| return 1; |
| } |
| remainder = round(remainder / lastwant.factor) * lastwant.factor; |
| } |
| else /* first unit is last unit */ |
| remainder = round(remainder / want.factor) * want.factor; |
| |
| round_dir = remainder - value; |
| } |
| if (flags.verbose == 2) { |
| removespaces(havestr); |
| logprintf("\t%s = ", havestr); |
| } else if (flags.verbose == 1) |
| logputchar('\t'); |
| } /* end if first unit */ |
| |
| if (0==(sigdigits = getsigdigits(have->factor, remainder, 10))) |
| break; /* nothing left */ |
| |
| /* Remove sub-precision junk accumulating in the remainder. Rounding |
| is base 2 to ensure that we keep all valid bits. */ |
| remainder = round_digits(remainder, |
| getsigdigits(have->factor,remainder,2),2); |
| if (nextunitstr) |
| remainder = want.factor * modf(remainder / want.factor, &value); |
| else |
| value = remainder / want.factor; |
| |
| /* The remainder represents less than one of the current want unit. |
| But with display rounding it may round up to 1, leading to an output |
| like "4 feet + 12 inch". Check for this case and if the remainder |
| indeed rounds up to 1 then add that remainder into the current unit |
| and set the remainder to zero. */ |
| if (nextunitstr){ |
| double rounded_next = |
| round_digits(remainder/want.factor, |
| getsigdigits(have->factor, remainder / want.factor, 10), |
| 10); |
| if (displays_as(1,rounded_next, NULL)){ |
| value++; |
| remainder = 0; /* Remainder is zero */ |
| } |
| } |
| |
| /* Round the value to significant digits to prevent display |
| of bogus sub-precision decimal digits */ |
| value = round_digits(value,sigdigits,10); |
| |
| /* This ensures that testing value against zero will work below |
| at the last unit, which is the only case where value is not integer */ |
| if (!nextunitstr && displays_as(0, value, NULL)) |
| value=0; |
| |
| if (!flags.verbose){ |
| if (!firstunit) |
| logputchar(UNITSEPCHAR); |
| logprintf(num_format.format,value); |
| value_shown=1; |
| } else { /* verbose case */ |
| if (value != 0) { |
| if (value_shown) /* have already displayed a number so need a '+' */ |
| logprintf(" %c ",val_sign); |
| else if (val_sign=='-') |
| logputs("-"); |
| showunitname(value, wantstr, PRINTNUM); |
| if (sigdigits <= floor(log10(value))+1) /* Used all sig digits */ |
| logprintf(" (at %d-digit precision limit)", DBL_DIG); |
| value_shown=1; |
| } |
| } |
| |
| freeunit(&want); |
| lastunitstr = wantstr; |
| wantstr = nextunitstr; |
| firstunit = 0; |
| } |
| |
| /* if the final unit value was rounded print indication */ |
| if (!value_shown) { /* provide output if every value rounded to zero */ |
| logputs("0 "); |
| if (isdecimal(*lastunitstr)) |
| logputs("* "); |
| logputs(lastunitstr); |
| } |
| |
| if (round_dir != 0) { |
| if (flags.verbose){ |
| if (round_dir > 0) |
| logprintf(" (rounded up to nearest %s) ", lastunitstr); |
| else |
| logprintf(" (rounded down to nearest %s) ", lastunitstr); |
| } else |
| logprintf("%c%c", UNITSEPCHAR, round_dir > 0 ?'-':'+'); |
| } |
| logputchar('\n'); |
| return 0; |
| } /* end showunitlist */ |
| |
| |
| #if defined (_WIN32) && defined (HAVE_MKS_TOOLKIT) |
| int |
| ismksmore(char *pager) |
| { |
| static int mksmore = -1; |
| |
| if (mksmore >= 0) |
| return mksmore; |
| |
| /* |
| Tries to determine whether the MKS Toolkit version of more(1) or |
| less(1) will run. In older versions of the Toolkit, neither |
| accepted '+<lineno>', insisting on the POSIX-compliant '+<lineno>g'. |
| */ |
| if (strstr(pager, "more") || strstr(pager, "less")) { |
| char *mypager, *mkspager, *mksroot, *p; |
| char pathbuf[FILENAME_MAX + 1]; |
| struct _stat mybuf, mksbuf; |
| |
| mypager = NULL; |
| mkspager = NULL; |
| mksmore = 0; |
| if (strlen(pager) > FILENAME_MAX) { |
| fprintf(stderr, "%s: cannot invoke pager--value '%s' in PAGER too long\n", |
| progname, pager); |
| return 0; /* TODO: this really isn't the right value */ |
| } |
| else if (!isfullpath(pager)) { |
| mypager = (char *) mymalloc(strlen(pager) + strlen(EXE_EXT) + 1, "(ishelpquery)"); |
| strcpy(mypager, pager); |
| if (!((p = strrchr(mypager, '.')) && isexe(p))) |
| strcat(mypager, EXE_EXT); |
| |
| _searchenv(mypager, "PATH", pathbuf); |
| } |
| else |
| strcpy(pathbuf, pager); |
| |
| mksroot = getenv("ROOTDIR"); |
| if (mksroot) { |
| char * mksprog; |
| |
| if (strstr(pager, "more")) |
| mksprog = "more.exe"; |
| else |
| mksprog = "less.exe"; |
| mkspager = (char *) mymalloc(strlen(mksroot) + strlen("/mksnt/") + strlen(mksprog) + 1, |
| "(ishelpquery)"); |
| strcpy(mkspager, mksroot); |
| strcat(mkspager, "\\mksnt\\"); |
| strcat(mkspager, mksprog); |
| } |
| |
| if (*pathbuf && mksroot) { |
| if (_stat(mkspager, &mksbuf)) { |
| fprintf(stderr, "%s: cannot stat file '%s'. ", progname, mkspager); |
| perror((char *) NULL); |
| return 0; |
| } |
| if (_stat(pathbuf, &mybuf)) { |
| fprintf(stderr, "%s: cannot stat file '%s'. ", progname, pathbuf); |
| perror((char *) NULL); |
| return 0; |
| } |
| /* |
| if we had inodes, this would be simple ... but if it walks |
| like a duck and swims like a duck and quacks like a duck ... |
| */ |
| if (mybuf.st_size == mksbuf.st_size |
| && mybuf.st_ctime == mksbuf.st_ctime |
| && mybuf.st_mtime == mksbuf.st_mtime |
| && mybuf.st_atime == mksbuf.st_atime |
| && mybuf.st_mode == mksbuf.st_mode) |
| mksmore = 1; |
| } |
| if (mypager) |
| free(mypager); |
| if (mkspager) |
| free(mkspager); |
| } |
| |
| return mksmore; |
| } |
| #endif |
| |
| /* |
| Checks to see if the input string contains HELPCOMMAND possibly |
| followed by a unit name on which help is sought. If not, then |
| return 0. Otherwise invoke the pager on units file at the line |
| where the specified unit is defined. Then return 1. */ |
| |
| int |
| ishelpquery(char *str, struct unittype *have) |
| { |
| struct unitlist *unit; |
| struct func *function; |
| struct wantalias *alias; |
| struct prefixlist *prefix; |
| char commandbuf[1000]; /* Hopefully this is enough overkill as no bounds */ |
| int unitline; /* checking is performed. */ |
| char *file; |
| char **exitptr; |
| char *commandstr; /* command string varies with OS */ |
| |
| if (have && !strcmp(str, UNITMATCH)){ |
| tryallunits(have,0); |
| return 1; |
| } |
| for(exitptr=exit_commands;*exitptr;exitptr++) |
| if (!strcmp(str, *exitptr)) |
| exit(EXIT_SUCCESS); |
| if (startswith(str, SEARCHCOMMAND)){ |
| str+=strlen(SEARCHCOMMAND); |
| if (!emptystr(str) && *str != ' ') |
| return 0; |
| removespaces(str); |
| if (emptystr(str)){ |
| printf("\n\ |
| Type 'search text' to see a list of all unit names \n\ |
| containing 'text' as a substring\n\n"); |
| return 1; |
| } |
| tryallunits(0,str); |
| return 1; |
| } |
| if (startswith(str, HELPCOMMAND)){ |
| str+=strlen(HELPCOMMAND); |
| if (!emptystr(str) && *str != ' ') |
| return 0; |
| removespaces(str); |
| |
| if (emptystr(str)){ |
| int nlines; |
| char *unitsfile; |
| char *msg = "\n\ |
| %s converts between different measuring systems and %s6 inches\n\ |
| acts as a units-aware calculator. At the '%s' %scm\n\ |
| prompt, type in the units you want to convert from or * 15.24\n\ |
| an expression to evaluate. At the '%s' prompt, / 0.065\n\ |
| enter the units to convert to or press return to see\n\ |
| the reduced form or definition. %stempF(75)\n\ |
| %stempC\n\ |
| The first example shows that 6 inches is about 15 cm 23.889\n\ |
| or (1/0.065) cm. The second example shows how to\n\ |
| convert 75 degrees Fahrenheit to Celsius. The third %sbu^(1/3)\n\ |
| example converts the cube root of a bushel to a list %sft;in\n\ |
| of semicolon-separated units. 1 ft + 0.9 in\n\ |
| \n\ |
| To quit from %s type 'quit' or 'exit'. %s2 btu + 450 ft lbf\n\ |
| %s(kg^2/s)/(day lb/m^2)\n\ |
| At the '%s' prompt type '%s' to get a * 1.0660684e+08\n\ |
| list of conformable units. At either prompt you / 9.3802611e-09\n\ |
| type 'help myunit' to browse the units database\n\ |
| and read the comments relating to myunit or see %s6 tbsp sugar\n\ |
| other units related to myunit. Typing 'search %sg\n\ |
| text' will show units whose names contain 'text'. * 75\n\ |
| / 0.013333333\n\n"; |
| char *fmsg = "To learn about the available units look in '%s'\n\n"; |
| FILE *fp; |
| |
| /* presumably, this cannot fail because it was already checked at startup */ |
| unitsfile = findunitsfile(NOERRMSG); |
| |
| nlines = countlines(msg); |
| /* but check again anyway ... */ |
| if (unitsfile) |
| nlines += countlines(fmsg); |
| |
| /* see if we need a pager */ |
| fp = get_output_fp(nlines); |
| |
| fprintf(fp, msg, |
| progname, QUERYHAVE, |
| QUERYHAVE, QUERYWANT, |
| QUERYWANT, |
| QUERYHAVE,QUERYWANT,QUERYHAVE,QUERYWANT, |
| progname, QUERYHAVE,QUERYWANT, |
| QUERYWANT, |
| UNITMATCH, |
| QUERYHAVE,QUERYWANT); |
| |
| if (unitsfile) |
| fprintf(fp, fmsg, unitsfile); |
| |
| if (fp != stdout) |
| pclose(fp); |
| |
| return 1; |
| } |
| if ((function = fnlookup(str))){ |
| file = function->file; |
| unitline = function->linenumber; |
| } |
| else if ((unit = ulookup(str))){ |
| unitline = unit->linenumber; |
| file = unit->file; |
| } |
| else if ((prefix = plookup(str)) && strlen(str)==prefix->len){ |
| unitline = prefix->linenumber; |
| file = prefix->file; |
| } |
| else if ((alias = aliaslookup(str))){ |
| unitline = alias->linenumber; |
| file = alias->file; |
| } |
| else { |
| printf("Unknown unit '%s'\n",str); |
| return 1; |
| } |
| |
| /* |
| With Microsoft compilers, system() uses cmd.exe. |
| Inner escaped quotes are necessary for filenames with spaces; outer |
| escaped quotes are necessary for cmd.exe to see the command as a |
| single string containing one or more quoted strings |
| (e.g., cmd /c ""command" "arg1" "arg2" ... ") |
| */ |
| #if defined (_WIN32) |
| #if defined (HAVE_MKS_TOOLKIT) |
| if (strstr(pager, "pg")) |
| commandstr = "\"\"%s\" +%d \"%s\"\""; |
| else if (ismksmore(pager)) { |
| /* |
| use the POSIX-compliant '+<number>g' for compatibility with older |
| versions of the Toolkit that don't accept '+<number>' |
| */ |
| commandstr = "\"\"%s\" +%dg \"%s\"\""; |
| } |
| else { |
| /* |
| more.com apparently takes the number as an offset rather than a |
| line number, so that '+1' starts at line 2 of the file. |
| more.com also cannot back up, so allow two lines of preceding |
| context. |
| */ |
| unitline -= 3; |
| if (unitline < 0) |
| unitline = 0; |
| commandstr = "\"\"%s\" +%d \"%s\"\""; |
| } |
| #else /* more.com is probably the only option */ |
| unitline -= 3; |
| if (unitline < 0) |
| unitline = 0; |
| commandstr = "\"\"%s\" +%d \"%s\"\""; |
| #endif |
| #else /* *nix */ |
| commandstr = "%s +%d %s"; |
| #endif |
| sprintf(commandbuf, commandstr, pager, unitline, file); |
| if (system(commandbuf)) |
| fprintf(stderr,"%s: cannot invoke pager '%s' to display help\n", |
| progname, pager); |
| return 1; |
| } |
| return 0; |
| } |
| |
| |
| #ifdef SUPPORT_UTF8 |
| void |
| checklocale() |
| { |
| char *temp; |
| temp = setlocale(LC_CTYPE,""); |
| utf8mode = (strcmp(nl_langinfo(CODESET),"UTF-8")==0); |
| if (temp){ |
| mylocale = dupstr(temp); |
| temp = strchr(mylocale,'.'); |
| if (temp) |
| *temp = 0; |
| } else |
| mylocale = DEFAULTLOCALE; |
| } |
| |
| #else |
| |
| void |
| checklocale() |
| { |
| char *temp=0; |
| /* environment overrides system */ |
| temp = getenv("LC_CTYPE"); |
| if (!temp) |
| temp = getenv("LC_ALL"); |
| if (!temp) |
| temp = getenv("LANG"); |
| #ifndef NO_SETLOCALE |
| if (temp) |
| temp = setlocale(LC_CTYPE,temp); |
| if (!temp) |
| temp = setlocale(LC_CTYPE,""); /* try system default */ |
| #endif |
| if (!temp) |
| mylocale = DEFAULTLOCALE; |
| else { |
| mylocale = dupstr(temp); |
| temp = strchr(mylocale,'.'); |
| if (temp) |
| *temp = 0; |
| } |
| } |
| |
| #endif |
| |
| /* |
| Replaces an alias in the specified string input. Returns 1 if the |
| alias that is found contains errors. |
| */ |
| |
| int |
| replacealias(char **string, int *buflen) |
| { |
| int usefree = 1; |
| struct wantalias *aliasptr; |
| char *input; |
| |
| if (!flags.readline && buflen) |
| usefree = 0; |
| |
| if (nonempty(*string)) { /* check that string is defined and nonempty */ |
| input = *string; |
| removespaces(input); |
| if ((aliasptr=aliaslookup(input))){ |
| if (checkunitlist(aliasptr->definition,NOERRMSG)){ |
| puts("Unit list definition contains errors."); |
| return 1; |
| } |
| if (usefree){ |
| free(*string); |
| *string = dupstr(aliasptr->definition); |
| } else { /* coverity[dead_error_line] */ |
| while (strlen(aliasptr->definition)>*buflen) |
| growbuffer(string, buflen); |
| strcpy(*string, aliasptr->definition); |
| } |
| } |
| } |
| return 0; |
| } |
| |
| |
| /* |
| Remaps the locale name returned on Windows systems to the value |
| returned on Unix-like systems |
| */ |
| |
| void |
| remaplocale(char *filename) |
| { |
| FILE *map; |
| char *value; |
| char name[80]; |
| |
| map = openfile(filename,"rt"); |
| if (!map) { |
| fprintf(stderr,"%s: cannot open locale map '%s'. ",progname,filename); |
| perror((char *) NULL); |
| } |
| else { |
| while(!feof(map)){ |
| if (!fgets(name,80,map)) |
| break; |
| lastchar(name) = 0; |
| value=strchr(name,'#'); |
| if (value) *value=0; |
| value=strchr(name,'\t'); |
| if (!value) continue; |
| *value++=0; |
| removespaces(value); |
| removespaces(name); |
| if (!strcmp(name, mylocale)) |
| mylocale = dupstr(value); |
| } |
| fclose(map); |
| } |
| } |
| |
| |
| void |
| close_logfile(void) |
| { |
| if (logfile){ |
| fputc('\n',logfile); |
| fclose(logfile); |
| } |
| } |
| |
| |
| void |
| open_logfile(void) |
| { |
| time_t logtime; |
| char * timestr; |
| |
| logfile = openfile(logfilename, "at"); |
| if (!logfile){ |
| fprintf(stderr, "%s: cannot write to log file '%s'. ", |
| progname, logfilename); |
| perror(0); |
| exit(EXIT_FAILURE); |
| } |
| time(&logtime); |
| timestr = ctime(&logtime); |
| fprintf(logfile, "### Log started %s \n", timestr); |
| atexit(close_logfile); |
| } |
| |
| void |
| write_files_sig(int sig) |
| { |
| #ifdef READLINE |
| if (historyfile) |
| save_history(); |
| #endif |
| close_logfile(); |
| signal(sig, SIG_DFL); |
| raise(sig); |
| } |
| |
| |
| |
| int test_int(int a, int b) |
| { |
| return a + b; |
| } |
| |
| char *g_argv_0 = NULL; |
| |
| |
| void conversion_setup(); |
| |
| #define exit return |
| int |
| conversion_worker(char *havestr, char *wantstr) |
| { |
| struct func *funcval; |
| struct wantalias *alias; |
| struct unittype have; |
| struct unittype want; |
| |
| conversion_setup(); |
| |
| replacectrlchars(havestr); |
| if (wantstr) |
| replacectrlchars(wantstr); |
| #ifdef SUPPORT_UTF8 |
| if (strwidth(havestr)<0){ |
| printf("Error: %s on input\n",invalid_utf8); |
| exit(EXIT_FAILURE); |
| } |
| if (wantstr && strwidth(wantstr)<0){ |
| printf("Error: %s on input\n",invalid_utf8); |
| exit(EXIT_FAILURE); |
| } |
| #endif |
| replace_operators(havestr); |
| removespaces(havestr); |
| if (wantstr) { |
| replace_operators(wantstr); |
| removespaces(wantstr); |
| } |
| if ((funcval = fnlookup(havestr))){ |
| showfuncdefinition(funcval, FUNCTION); |
| exit(EXIT_SUCCESS); |
| } |
| if ((funcval = invfnlookup(havestr))){ |
| showfuncdefinition(funcval, INVERSE); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if ((alias = aliaslookup(havestr))){ |
| showunitlistdef(alias); |
| exit(EXIT_SUCCESS); |
| } |
| if (processunit(&have, havestr, NOPOINT)) |
| exit(EXIT_FAILURE); |
| if (flags.showconformable == 1) { |
| tryallunits(&have,0); |
| exit(EXIT_SUCCESS); |
| } |
| if (!wantstr){ |
| showdefinition(havestr,&have); |
| exit(EXIT_SUCCESS); |
| } |
| if (replacealias(&wantstr, 0)) /* the 0 says that we can free wantstr */ |
| exit(EXIT_FAILURE); |
| if ((funcval = fnlookup(wantstr))){ |
| if (showfunc(havestr, &have, funcval)) /* Clobbers have */ |
| exit(EXIT_FAILURE); |
| else |
| exit(EXIT_SUCCESS); |
| } |
| if (processwant(&want, wantstr, NOPOINT)) |
| exit(EXIT_FAILURE); |
| if (strchr(wantstr, UNITSEPCHAR)){ |
| if (showunitlist(havestr, &have, wantstr)) |
| exit(EXIT_FAILURE); |
| else |
| exit(EXIT_SUCCESS); |
| } |
| if (showanswer(havestr,&have,wantstr,&want)) |
| exit(EXIT_FAILURE); |
| else |
| exit(EXIT_SUCCESS); |
| } |
| #undef exit |
| |
| |
| void |
| conversion_setup() |
| { |
| static int setup = 0; |
| |
| if (setup) |
| return; |
| |
| flags.quiet = 1; /* Do not supress prompting */ |
| flags.unitcheck = 0; /* Unit checking is off */ |
| flags.verbose = 2; /* Medium verbosity */ |
| flags.round = 0; /* Rounding off */ |
| flags.strictconvert=0; /* Strict conversion disabled (reciprocals active) */ |
| flags.unitlists = 1; /* Multi-unit conversion active */ |
| flags.oneline = 1; /* One line output is disabled */ |
| flags.showconformable=0; /* show unit conversion rather than all conformable units */ |
| flags.showfactor = 0; /* Don't show a multiplier for a 1|x fraction */ |
| /* in unit list output */ |
| parserflags.minusminus = 1; /* '-' character gives subtraction */ |
| parserflags.oldstar = 0; /* '*' has same precedence as '/' */ |
| |
| progname = getprogramname(g_argv_0); |
| |
| |
| |
| if (!(isfullpath(UNITSFILE) && isfullpath(LOCALEMAP))) |
| progdir = getprogdir(g_argv_0, &fullprogname); |
| else { |
| progdir = NULL; |
| fullprogname = NULL; |
| } |
| datadir = getdatadir(); /* directory to search as last resort */ |
| |
| checklocale(); |
| |
| homedir = findhome(&homedir_error); |
| |
| unitsfiles[0] = 0; |
| |
| if (!unitsfiles[0]){ |
| char *unitsfile; |
| unitsfile = findunitsfile(ERRMSG); |
| if (!unitsfile) |
| exit(EXIT_FAILURE); |
| else { |
| int file_exists; |
| |
| unitsfiles[0] = unitsfile; |
| unitsfiles[1] = personalfile(HOME_UNITS_ENV,homeunitsfile, |
| 0, &file_exists); |
| unitsfiles[2] = 0; |
| } |
| } |
| |
| char **unitfileptr; |
| for(unitfileptr=unitsfiles;*unitfileptr;unitfileptr++){ |
| int unitcount, prefixcount, funccount; |
| int readerr = readunits(*unitfileptr, stderr, &unitcount, &prefixcount, |
| &funccount, 0); |
| if (readerr==E_MEMORY || readerr==E_FILE) |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| |
| void |
| do_a_conversion(char *input, char *output, char *system) |
| { |
| char *inp, *out; |
| |
| char units[] = "./units"; |
| |
| g_argv_0 = units; |
| |
| checklocale(); |
| |
| num_format.format = NULL; |
| num_format.precision = DEFAULTPRECISION; |
| num_format.type = DEFAULTTYPE; |
| |
| if (num_format.format != NULL) { |
| if (parsenumformat()) |
| exit(EXIT_FAILURE); |
| } |
| else |
| setnumformat(); |
| |
| setenv("UNITS_SYSTEM", system, 1); |
| printf("units system %s\n", getenv("UNITS_SYSTEM")); |
| |
| conversion_worker(input, output); |
| } |
| |
| |
| int test_main() |
| { |
| char a[] = "pi", b[] = ""; |
| do_a_conversion(a, b, "si"); |
| do_a_conversion(a, b, "si"); |
| do_a_conversion(a, b, "si"); |
| } |
| |
| |
| int |
| old_main(int argc, char **argv) |
| { |
| static struct unittype have, want; |
| char *havestr=0, *wantstr=0; |
| int leading_spaces; |
| struct func *funcval; |
| struct wantalias *alias; |
| int havestrsize=0; /* Only used if READLINE is undefined */ |
| int wantstrsize=0; /* Only used if READLINE is undefined */ |
| int readerr; |
| char **unitfileptr; |
| int unitcount=0, prefixcount=0, funccount=0; /* for counting units */ |
| char *queryhave, *querywant, *comment; |
| int queryhavewidth, querywantwidth; |
| #ifdef _WIN32 |
| char *localemap; |
| #endif |
| |
| /* Set program parameter defaults */ |
| num_format.format = NULL; |
| num_format.precision = DEFAULTPRECISION; |
| num_format.type = DEFAULTTYPE; |
| |
| flags.quiet = 0; /* Do not supress prompting */ |
| flags.unitcheck = 0; /* Unit checking is off */ |
| flags.verbose = 1; /* Medium verbosity */ |
| flags.round = 0; /* Rounding off */ |
| flags.strictconvert=0; /* Strict conversion disabled (reciprocals active) */ |
| flags.unitlists = 1; /* Multi-unit conversion active */ |
| flags.oneline = 0; /* One line output is disabled */ |
| flags.showconformable=0; /* show unit conversion rather than all conformable units */ |
| flags.showfactor = 0; /* Don't show a multiplier for a 1|x fraction */ |
| /* in unit list output */ |
| parserflags.minusminus = 1; /* '-' character gives subtraction */ |
| parserflags.oldstar = 0; /* '*' has same precedence as '/' */ |
| |
| progname = getprogramname(argv[0]); |
| |
| /* |
| unless UNITSFILE and LOCALEMAP have absolute pathnames, we may need |
| progdir to search for supporting files |
| */ |
| if (!(isfullpath(UNITSFILE) && isfullpath(LOCALEMAP))) |
| progdir = getprogdir(argv[0], &fullprogname); |
| else { |
| progdir = NULL; |
| fullprogname = NULL; |
| } |
| datadir = getdatadir(); /* directory to search as last resort */ |
| |
| checklocale(); |
| |
| homedir = findhome(&homedir_error); |
| |
| #ifdef READLINE |
| # if RL_READLINE_VERSION > 0x0402 |
| rl_completion_entry_function = (rl_compentry_func_t *)completeunits; |
| # else |
| rl_completion_entry_function = (Function *)completeunits; |
| # endif |
| rl_basic_word_break_characters = " \t+-*/()|^;"; |
| flags.readline = isatty(0); |
| if (flags.readline){ |
| int file_exists; |
| historyfile = personalfile(NULL,HISTORY_FILE,1,&file_exists); |
| } |
| #else |
| flags.readline = 0; |
| #endif |
| |
| unitsfiles[0] = 0; |
| |
| #ifdef _WIN32 |
| if (!strcmp(homeunitsfile,".units")) |
| homeunitsfile = "unitdef.units"; |
| #endif |
| |
| pager = getenv("PAGER"); |
| if (!pager) |
| pager = DEFAULTPAGER; |
| |
| flags.interactive = processargs(argc, argv, &havestr, &wantstr); |
| |
| #ifdef READLINE |
| if (flags.interactive && flags.readline && historyfile){ |
| rl_initialize(); |
| read_history(historyfile); |
| init_history_length = history_length; |
| init_history_base = history_base; |
| atexit(save_history); |
| } |
| #endif |
| |
| signal(SIGINT, write_files_sig); |
| signal(SIGTERM, write_files_sig); |
| #ifdef SIGQUIT |
| signal(SIGQUIT, SIG_IGN); /* Ignore QUIT signal sent by Ctrl-\ or Ctrl-4 */ |
| #endif |
| if (logfilename) { |
| if (!flags.interactive) |
| fprintf(stderr, |
| "Log file '%s' ignored in non-interactive mode.\n",logfilename); |
| else open_logfile(); |
| } |
| |
| /* user has specified the complete format--use it */ |
| if (num_format.format != NULL) { |
| if (parsenumformat()) |
| exit(EXIT_FAILURE); |
| } |
| else |
| setnumformat(); |
| |
| if (flags.verbose==0) |
| deftext = ""; |
| |
| if (!unitsfiles[0]){ |
| char *unitsfile; |
| unitsfile = findunitsfile(ERRMSG); |
| if (!unitsfile) |
| exit(EXIT_FAILURE); |
| else { |
| int file_exists; |
| |
| unitsfiles[0] = unitsfile; |
| unitsfiles[1] = personalfile(HOME_UNITS_ENV,homeunitsfile, |
| 0, &file_exists); |
| unitsfiles[2] = 0; |
| } |
| } |
| |
| #ifdef _WIN32 |
| localemap = findlocalemap(ERRMSG); |
| if (localemap) |
| remaplocale(localemap); |
| #endif |
| |
| for(unitfileptr=unitsfiles;*unitfileptr;unitfileptr++){ |
| readerr = readunits(*unitfileptr, stderr, &unitcount, &prefixcount, |
| &funccount, 0); |
| if (readerr==E_MEMORY || readerr==E_FILE) |
| exit(EXIT_FAILURE); |
| } |
| |
| if (flags.quiet) |
| queryhave = querywant = ""; /* No prompts are being printed */ |
| else { |
| if (!promptprefix){ |
| queryhave = QUERYHAVE; |
| querywant = QUERYWANT; |
| } else { |
| queryhave = (char *)mymalloc(strlen(promptprefix)+strlen(QUERYHAVE)+1, |
| "(main)"); |
| querywant = (char *)mymalloc(strlen(promptprefix)+strlen(QUERYWANT)+1, |
| "(main)"); |
| strcpy(queryhave, promptprefix); |
| strcat(queryhave, QUERYHAVE); |
| memset(querywant, ' ', strlen(promptprefix)); |
| strcpy(querywant+strlen(promptprefix), QUERYWANT); |
| } |
| printf("%d units, %d prefixes, %d nonlinear units\n\n", |
| unitcount, prefixcount,funccount); |
| } |
| queryhavewidth = strwidth(queryhave); |
| querywantwidth = strwidth(querywant); |
| |
| if (flags.unitcheck) { |
| checkunits(flags.unitcheck==2 || flags.verbose==2); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!flags.interactive) { |
| replacectrlchars(havestr); |
| if (wantstr) |
| replacectrlchars(wantstr); |
| #ifdef SUPPORT_UTF8 |
| if (strwidth(havestr)<0){ |
| printf("Error: %s on input\n",invalid_utf8); |
| exit(EXIT_FAILURE); |
| } |
| if (wantstr && strwidth(wantstr)<0){ |
| printf("Error: %s on input\n",invalid_utf8); |
| exit(EXIT_FAILURE); |
| } |
| #endif |
| replace_operators(havestr); |
| removespaces(havestr); |
| if (wantstr) { |
| replace_operators(wantstr); |
| removespaces(wantstr); |
| } |
| if ((funcval = fnlookup(havestr))){ |
| showfuncdefinition(funcval, FUNCTION); |
| exit(EXIT_SUCCESS); |
| } |
| if ((funcval = invfnlookup(havestr))){ |
| showfuncdefinition(funcval, INVERSE); |
| exit(EXIT_SUCCESS); |
| } |
| if ((alias = aliaslookup(havestr))){ |
| showunitlistdef(alias); |
| exit(EXIT_SUCCESS); |
| } |
| if (processunit(&have, havestr, NOPOINT)) |
| exit(EXIT_FAILURE); |
| if (flags.showconformable == 1) { |
| tryallunits(&have,0); |
| exit(EXIT_SUCCESS); |
| } |
| if (!wantstr){ |
| showdefinition(havestr,&have); |
| exit(EXIT_SUCCESS); |
| } |
| if (replacealias(&wantstr, 0)) /* the 0 says that we can free wantstr */ |
| exit(EXIT_FAILURE); |
| if ((funcval = fnlookup(wantstr))){ |
| if (showfunc(havestr, &have, funcval)) /* Clobbers have */ |
| exit(EXIT_FAILURE); |
| else |
| exit(EXIT_SUCCESS); |
| } |
| if (processwant(&want, wantstr, NOPOINT)) |
| exit(EXIT_FAILURE); |
| if (strchr(wantstr, UNITSEPCHAR)){ |
| if (showunitlist(havestr, &have, wantstr)) |
| exit(EXIT_FAILURE); |
| else |
| exit(EXIT_SUCCESS); |
| } |
| if (showanswer(havestr,&have,wantstr,&want)) |
| exit(EXIT_FAILURE); |
| else |
| exit(EXIT_SUCCESS); |
| } else { |
| /******************/ |
| /** interactive **/ |
| /******************/ |
| for (;;) { |
| do { |
| fflush(stdout); |
| getuser(&havestr,&havestrsize,queryhave); |
| replace_operators(havestr); |
| comment = strip_comment(havestr); |
| leading_spaces = strspn(havestr," "); |
| removespaces(havestr); |
| if (logfile && comment && emptystr(havestr)) |
| fprintf(logfile, "#%s\n", comment); |
| } while (emptystr(havestr) |
| || ishelpquery(havestr,0) |
| || definevariable(havestr,queryhavewidth+leading_spaces) |
| || (!fnlookup(havestr) |
| && !invfnlookup(havestr) |
| && !aliaslookup(havestr) |
| && processunit(&have, havestr, queryhavewidth+leading_spaces))); |
| if (logfile) { |
| if (comment) |
| fprintf(logfile, "%s%s\t#%s\n", LOGFROM, havestr,comment); |
| else |
| fprintf(logfile, "%s%s\n", LOGFROM, havestr); |
| } |
| if ((alias = aliaslookup(havestr))){ |
| showunitlistdef(alias); |
| continue; |
| } |
| if ((funcval = fnlookup(havestr))){ |
| showfuncdefinition(funcval, FUNCTION); |
| continue; |
| } |
| if ((funcval = invfnlookup(havestr))){ |
| showfuncdefinition(funcval, INVERSE); |
| continue; |
| } |
| do { |
| int repeat; |
| do { |
| repeat = 0; |
| fflush(stdout); |
| getuser(&wantstr,&wantstrsize,querywant); |
| replace_operators(wantstr); |
| comment = strip_comment(wantstr); |
| leading_spaces = strspn(wantstr," "); |
| removespaces(wantstr); |
| if (logfile && comment && emptystr(wantstr)){ |
| fprintf(logfile, "#%s\n", comment); |
| repeat = 1; |
| } |
| if (ishelpquery(wantstr, &have)){ |
| repeat = 1; |
| printf("%s%s\n",queryhave, havestr); |
| } |
| } while (repeat); |
| } while (replacealias(&wantstr, &wantstrsize) |
| || (!fnlookup(wantstr) |
| && processwant(&want, wantstr, querywantwidth+leading_spaces))); |
| if (logfile) { |
| fprintf(logfile, "%s", LOGTO); |
| tightprint(logfile, wantstr); |
| if (comment) |
| fprintf(logfile, "\t#%s", comment); |
| putc('\n', logfile); |
| } |
| if (emptystr(wantstr)) |
| showdefinition(havestr,&have); |
| else if (strchr(wantstr, UNITSEPCHAR)) |
| showunitlist(havestr, &have, wantstr); |
| else if ((funcval = fnlookup(wantstr))) |
| showfunc(havestr, &have, funcval); /* Clobbers have */ |
| else { |
| showanswer(havestr,&have,wantstr, &want); |
| freeunit(&want); |
| } |
| unitcopy(&lastunit, &have); |
| lastunitset=1; |
| freeunit(&have); |
| } |
| } |
| return (0); |
| } |
| |
| /* NOTES: |
| |
| mymalloc, growbuffer and tryallunits are the only places with print |
| statements that should (?) be removed to make a library. How can |
| error reporting in these functions (memory allocation errors) be |
| handled cleanly for a library implementation? |
| |
| Way to report the reduced form of the two operands of a sum when |
| they are not conformable. |
| |
| Way to report the required domain when getting an domain error. |
| |
| */ |