Initial commit
diff --git a/units.c b/units.c
new file mode 100644
index 0000000..124b622
--- /dev/null
+++ b/units.c
@@ -0,0 +1,6760 @@
+#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."
+
+#define _XOPEN_SOURCE 600
+
+#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 <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;
+}
+
+
+
+void
+logprintf(const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ if (logfile) {
+ va_start(args, format);
+ vfprintf(logfile, format, args);
+ va_end(args);
+ }
+}
+
+void
+logputchar(char c)
+{
+ putchar(c);
+ if (logfile) fputc(c, logfile);
+}
+
+void
+logputs(const char *s)
+{
+ fputs(s, stdout);
+ 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;
+
+ setup = 1;
+
+ 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 *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();
+
+ conversion_worker(input, output);
+}
+
+
+int test_main()
+{
+ char a[] = "pi", b[] = "";
+ do_a_conversion(a, b);
+ do_a_conversion(a, b);
+ do_a_conversion(a, b);
+}
+
+
+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.
+
+*/