#define VERSION "2.02"

/*
 *  units, a program for units conversion
 *  Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
 *                2007, 2009, 2011, 2012
 *  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 _XOPEN_SOURCE 600

#include <ctype.h>
#include <float.h>

#include <stdio.h>
#include <signal.h>

#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
#  include <readline/readline.h>
#  include <readline/history.h>
#  define RVERSTR "with readline"
#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"
#endif 

#ifndef EOFCHAR
#  define EOFCHAR "D"
#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 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 */
#define DEFAULTPAGER "more"     /* Default pager program */
#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() */

/* 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() */


/* Key words for function definitions */

struct {
  char *word;
  char delimit;
} fnkeywords[]={ {"units=",  FUNCSEPCHAR}, 
                 {"domain=", ','}, 
                 {"range=", ','}, 
                 {0,0}};

#define FN_UNITS 0
#define FN_DOMAIN 1
#define FN_RANGE 2


struct {
  char *format;		/* printf() format specification for numeric output */
  int width;		/* printf() width from format */
  int precision;	/* printf() precision from format */
  int type;		/* printf() type from format */
} num_format;


struct {    /* Program command line option flags */
 int
   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 */
   showfactor,    /*   */
   strictconvert, /* Strict conversion (disables reciprocals) */
   unitcheck,     /* Enable unit checking: 1 for regular check, 2 for verbose*/
   verbose;       /* Flag for output verbosity */
} 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 *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 *progname;		        /* Used in error messages */
char *queryhave = "You have: "; /* Prompt text for units to convert from */
char *querywant = "You want: "; /* Prompt text for units to convert to */
char *deftext="\tDefinition: "; /* Output text when printing definition */
char *digits = "0123456789.,";

#define  HASHSIZE 101           /* Values from K&R 2nd ed., Sect. 6.6 */
#define  HASHNUMBER 31

#define  PREFIXTABSIZE 128
#define  prefixhash(str) (*(str) & 127)    /* "hash" value for prefixes */

char *errormsg[]={"Successful completion", 
                  "Parse error",           
                  "Product overflow",      
                  "Unit reduction error (bad unit definition)",
                  "Illegal sum or difference of non-conformable units",
                  "Unit not dimensionless",
		  "Unit not a root",
		  "Unknown unit",
		  "Bad argument",
		  "Weird nonlinear unit type (bug in program)",
		  "Function argument has wrong dimension",
		  "Argument of function outside domain",
		  "Nonlinear unit definition has unit error",
		  "No inverse defined",
		  "Parser memory overflow (recursive function definition?)",
		  "Argument wrong dimension or bad nonlinear unit definition",
                  "Unable to open units file",
		  "Units file contains errors",
		  "Memory allocation error",
                  "Malformed number",
                  "Unit name ends with nonzero digit without preceding '_'"
                  };

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 *last;	/* last item in list--only set in first item */
   struct prefixlist *next;   	/* next item in list */
} *ptab[PREFIXTABSIZE];


/* Functions are stored in a linked list */

struct func *firstfunc = 0;    /* Base of linked list of functions */
struct func *lastfunc = 0;     /* Last entry in linked list of functions */

/* 
   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;

char *NULLUNIT = "";  /* Used for units that are canceled during reduction */


struct wantalias {
  char *name;
  char *definition;
  struct wantalias *next;
  int linenumber;
  char *file;
};

struct wantalias *firstalias=0;
struct wantalias *lastalias=0;



/* 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(3); 
  }
}


/* 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, int length)
{
  length--;
  while(length>0){
    if (!strchr(digits, str[length]))
      return 0;
    length--;
    if (str[length]=='_')
      return 1;
  }
  return 0;
}


/* replace various Unicode minus symbols with ASCII hyphen-minus U+002D */
void
replace_minus(char *input)
{
  char *unicode_minus[] = {
    "\xE2\x80\x92", /* U+2012: figure dash */
    "\xE2\x80\x93", /* U+2013: en dash */
    "\xE2\x88\x92",  /* U+2212: minus */
    0
  };
  char *inptr, *outptr, *ptr, **minus;

  for (minus=unicode_minus; *minus; minus++) {
    inptr = outptr = input;
    do {
      ptr = strstr(inptr, *minus);  /* find next unicode minus */
      if (ptr) {
	while (inptr < ptr)   /* copy the input up to the minus symbol */
	  *outptr++ = *inptr++;
	*outptr++ = '-';        /* U+002D: hyphen-minus */
	inptr = ptr + strlen(*minus);
      }
    } while (ptr);
    /* if no replacements were made, the input isn't changed */
    if (inptr > input) {
      while (*inptr)
	*outptr++ = *inptr++;
      *outptr = '\0';
    }
  }
}


/* Replace all chars in badchars with a space */

void
spacechars(char *string, const char *badchars)
{
  while(*string){
    if (strchr(badchars, *string))
      *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 (buf[strlen(buf)-1] == '\\') {   /* If last char of buffer is \ then   */
    ungetc('\\', file);               /* we don't know if it is followed by */
    buf[strlen(buf)-1] = 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 ((*buf)[strlen(*buf)-1] != '\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. */ 
  spacechars(*buf, "\t\n\r");
  return *buf;
}

/* Allocates memory and aborts if malloc fails. */

void *
mymalloc(int bytes,char *mesg)
{
   void *pointer;

   pointer = malloc(bytes);
   if (!pointer){
     fprintf(stderr, "%s: memory allocation error %s\n", progname,mesg);
     exit(3);
   }
   return pointer;
}


/* Duplicates a string */

char *
dupstr(char *str)
{
   char *ret;

   ret = mymalloc(strlen(str) + 1,"(dupstr)");
   strcpy(ret, str);
   return (ret);
}

#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[prefixhash(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, int length)
{ 
  struct func *funcptr;

  for(funcptr=firstfunc;funcptr;funcptr = funcptr->next)
    if (length==strlen(funcptr->name) && 
	0==strncmp(funcptr->name,str,length))
      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)
{
  if (!lastfunc){
    firstfunc = newfunc;
    lastfunc = firstfunc;
  } else {
    lastfunc->next = newfunc;
    lastfunc = newfunc;
  }
  newfunc->next = 0;
}


/* 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 white space from the input */

char *
removepadding(char *in)
{
  char *endptr;

  in += strspn(in,WHITE);
  for(endptr = in + strlen(in) - 1;*in && strchr(WHITE,*endptr);endptr--)
    *endptr=0;
  return in;
}


/* Print string but replace two consecutive white space characters with
   one space. */
void
tightprint(FILE *outfile, char *string)
{
  char *p;
  int printedspace=0;
  for(p=string;*p;p++){
    if (strchr(WHITE,*p)){
      if (!printedspace){
	fputc(' ',outfile);
	printedspace=1;
      }
    } else {
      fputc(*p,outfile);
      printedspace=0;
    }
  }
}


/* 
   Checks whether the input string is a function name, possibly
   surrounded by white space.  Returns the function structure if one
   is found, or null otherwise.
*/

struct func *
isfunction(char *str)
{
  str = removepadding(str);
  return fnlookup(str,strlen(str));
}

#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 white space
  deliminted.  The second part is everything else.  Removes trailing
  white space 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, WHITE);
  if (*first){
    *second = strtok(0, "\n");
    if (*second){
      *second = removepadding(*second);
      if (!**second)
        *second = 0;
    }
  }
}


/* 
   Returns 1 if the input consists entirely of whitespace characters
   and returns 0 otherwise. 
*/

int
isblankstr(char *str)
{
  while(*str) if (!strchr(WHITE,*str++)) return 0;
  return 1;
}


int
newunit(char *unitname, char *unitdef, int *count, int linenum, 
        char *file,FILE *errfile)
{
  struct unitlist *uptr;
  unsigned hashval; 

  /* units ending with '_' create ambiguity for exponents */

  if (unitname[0]=='_' || unitname[strlen(unitname)-1]=='_'){
    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", unitname[strlen(unitname)-1]) 
      && !hassubscript(unitname,strlen(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 (strchr(digits, unitname[0])){
    if (errfile) fprintf(errfile,
        "%s: unit '%s' on line %d of '%s' ignored.  It starts with a digit\n", 
        progname, unitname, linenum, file);
    return E_BADFILE;
  }

  if ((uptr=ulookup(unitname))) {    /* Is it a redefinition? */
    if (flags.unitcheck && errfile)
      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)
{
  struct prefixlist *pfxptr;
  unsigned pval;

  unitname[strlen(unitname) - 1] = 0;
  if (strchr(digits, unitname[0])){
    if (errfile) fprintf(errfile,
	   "%s: unit '%s' on line %d of '%s' ignored.  It starts with a digit\n", 
	     progname, unitname, linenum, file);
    return E_BADFILE;
  }
  if ((pfxptr = plookup(unitname))     /* already there: redefinition */
      && !strcmp(pfxptr->name, unitname)){
    if (flags.unitcheck && errfile)
      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);
    pfxptr->next = NULL;
    pval = prefixhash(unitname);
    if (ptab[pval] == NULL)
      ptab[pval] = pfxptr;
    else
      ptab[pval]->last->next = pfxptr;
    ptab[pval]->last = 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, char delimiter,
	  char *unitname, int linenum, char *file,FILE *errfile)
{
  char *start, *end, *middle;

  start = strchr(input, '[');
  if (!start){
    if (errfile) fprintf(errfile,
	     "%s: expecting '[' in definition of '%s' in '%s' line %d\n",
	     progname, unitname, file, linenum);
    return 0;
  }
  *start++=0;
  if (!isblankstr(input)){
    if (errfile) fprintf(errfile,
        "%s: unexpected characters before '[' in definition of '%s' in '%s' line %d\n",
	     progname, unitname, file, linenum);
    return 0;
  }
  end = strchr(start, ']');
  if (!end){
    if (errfile) fprintf(errfile,
	     "%s: expecting ']' in definition of '%s' in '%s' line %d\n",
	     progname, unitname, file, linenum);
    return 0;
  }
  *end++=0;

  middle = strchr(start,delimiter);
  
  if (middle){
    *middle++=0;
    *second = removepadding(middle);
  } else
    *second = 0;
  
  *first = removepadding(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 (strlen(first)>0){
    val = strtod(first, &end);
    if (*end)
      return EI_ERR_MALF;
    else {
      *firstout=(double *)mymalloc(sizeof(double), "(extract_interval)");
      **firstout = val;
    }
  }
  if (second && strlen(second)>0){
    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;
}

#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)
{
  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;

  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 (strchr(digits, unitname[0])){
    if (errfile) fprintf(errfile,
            "%s: function '%s' on line %d of '%s' ignored.  It starts with a digit\n", 
            progname, unitname, linenum, file);
    return E_BADFILE;
  }

  if (!end || strlen(end)>1 || start==end){
    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;
  looking_for_keywords=1;
  while (looking_for_keywords) {
    looking_for_keywords = 0;
    for(i=0;fnkeywords[i].word;i++){
      if (!strncmp(fnkeywords[i].word, unitdef, strlen(fnkeywords[i].word))){
	looking_for_keywords = 1;  /* found keyword so keep looking */
        unitdef = parsepair(unitdef+strlen(fnkeywords[i].word), 
                            &first, &second, fnkeywords[i].delimit,
			    unitname, linenum, file,errfile);
	if (!unitdef){
	  FREE_STUFF;
	  return E_BADFILE;
	}
	unitdef = removepadding(unitdef);
	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);
          if (err)
	    FREE_STUFF;
          if (err==EI_ERR_DEC){
	    if (errfile) fprintf(errfile,
               "%s: domain decreasing 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);
          if (err)
	    FREE_STUFF;
          if (err==EI_ERR_DEC){
	    if (errfile) fprintf(errfile,
               "%s: range decreasing 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 (*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;
  }
  if ((funcentry=fnlookup(unitname,strlen(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->table = 0;
  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;
  inv = strchr(unitdef,FUNCSEPCHAR);
  if (inv)
    *inv++ = 0;
  funcentry->forward.param = dupstr(start);
  funcentry->forward.def = dupstr(removepadding(unitdef));
  if (inv){
    funcentry->inverse.def = dupstr(removepadding(inv));
    funcentry->inverse.param = dupstr(unitname);
  } 
  else 
    funcentry->inverse.def = 0;
  funcentry->linenumber = linenum;
  funcentry->file = file;
  return 0;
}


int 
newtable(char *unitname,char *unitdef, int *count,
           int linenum, char *file,FILE *errfile)
{
  char *start, *end;
  char *tableunit;
  int tablealloc, tabpt;
  struct pair *tab;
  struct func *funcentry;

                                      /* coverity[returned_null] */
  tableunit = strchr(unitname,'[');
  end = strchr(unitname,']');
  *tableunit++=0;
  if (strchr(digits, unitname[0])){
    if (errfile) fprintf(errfile,
	      "%s: unit '%s' on line %d ignored.  It starts with a digit\n", 
	      progname, unitname, linenum);
    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;
  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)
      break;
    if (tabpt>0 && tab[tabpt].location<=tab[tabpt-1].location){
      if (errfile)
	fprintf(errfile,"%s: points don't increase (%.8g to %.8g) 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," \t");
    tab[tabpt].value = strtod(start,&end);
    if (start==end){
      if (errfile)
	fprintf(errfile,"%s: missing value after %.8g in units file '%s' line %d\n",
		progname, tab[tabpt].location, file, linenum);
      free(tab);
      return E_BADFILE;
    }
    tabpt++;
    start=end+strspn(end," \t,");
  }
  if ((funcentry=fnlookup(unitname,strlen(unitname)))){
    if (flags.unitcheck && errfile)
      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->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);
    if (!lastalias){
      firstalias = aliasentry;
      lastalias = firstalias;
    } else {
      lastalias->next = aliasentry;
      lastalias = aliasentry;
    }
    aliasentry->next = 0;
  }
  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,WHITE);
  while (listitem){
    if (!strcmp(name, listitem))
      return 1;
    listitem = strtok(0,WHITE);
  }
  return 0;
}


#ifdef NO_SETENV
int
setenv(const char *name, const char *val, int overwrite)
{
  char *environ;

  if (!overwrite && getenv(name) != NULL)
    return 0;
  environ = (char *) malloc(strlen(name) + strlen(val) + 2);
  if (!environ)
    return 1;
  strcpy(environ, name);
  strcat(environ, "=");
  strcat(environ, val);

  /* putenv() doesn't copy its argument, so don't free environ */

#if defined _WIN32 && defined _MSC_VER
  return _putenv(environ);
#else
  return putenv(environ);
#endif
}
#endif


/* 
   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 len, linenum, linebufsize, goterr, retcode;
   int locunitcount, locprefixcount, locfunccount;
   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 = fopen(file, "rt");
   if (!unitfile) 
     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 && 0==strncmp(UTF8MARKER, line, strlen(UTF8MARKER))){ 
        int i;
        for(lineptr=line,i=0;i<strlen(UTF8MARKER);i++, lineptr++)
          *lineptr=' ';
      }
      if (-1 == strwidth(line)){
        readerror(errfile, "%s: %s on line %d of '%s'\n",
   		      progname, invalid_utf8, linenum, file);
        continue;
      }
      replace_minus(line);
      if ((lineptr = strchr(line,COMMENTCHAR))) /* strip comments */
         *lineptr = 0;

      if (*line == COMMANDCHAR) {         /* Process units file commands */
        unitname = strtok(line+1, WHITE);
        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,WHITE);
           unitdef=strtok(0,"\r\n");
 	  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, WHITE); 
	  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,"message")){
           unitname = strtok(0,"\r\n");
           if (!flags.quiet && unitname)
 	    printf("%s\n",unitname);
 	  continue; 
 	}
         else if (!strcmp(unitname,"set")) {
           unitname = strtok(0,WHITE);
 	  unitdef = strtok(0,WHITE);
 	  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, WHITE); 
	    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 (strchr(unitname, '/') || strchr(unitname, '\\'))
	      strcpy(includefile,unitname);
	    else {
              char *pathend;
	      strcpy(includefile, file);
	      pathend = includefile + strlen(includefile) - 1;
	      while(pathend != includefile && !strchr("/\\", *pathend))
		pathend--;
	      if (strchr("/\\", *pathend))
		pathend++;
	      strcpy(pathend, 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: unable to open included file '%s' at line %d of file '%s\n",
			progname, includefile, 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 (!unitdef){
        readerror(errfile,
  	      "%s: unit name without a definition at line %d of file '%s'\n",
	      progname, linenum, file);
        continue;
      }
      
      len = strlen(unitname);      

      if (unitname[len - 1] == '-'){ 	  /* it's a prefix definition */
	if (newprefix(unitname,unitdef,&locprefixcount,linenum,
		      permfile,errfile))
	  goterr=1;
      }
      else if (strchr(unitname,'[')){     /* table definition  */
        retcode=newtable(unitname,unitdef,&locfunccount,linenum,
			 permfile,errfile);
        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))
	  goterr = 1;
      }
      else {	                          /* ordinary unit definition */
        if (newunit(unitname,unitdef,&locunitcount,linenum,permfile,errfile))
	  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;

   printf(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)
	    printf("%s%d", powerstring, counter);
	 if (**ptr)
	    printf(" %s", *ptr);
	 counter = 1;
      }
   }
   if (counter > 1)
      printf("%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)
	    printf("%s%d", powerstring, counter);
	 if (**ptr) {
	    if (!printedslash)
	       printf(" /");
	    printedslash = 1;
	    printf(" %s", *ptr);
	 }
	 counter = 1;
      }
   }
   if (counter > 1)
      printf("%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 (strlen(unit)>2 && unit[strlen(unit) - 1] == 's') {
      copy = dupstr(unit);
      copy[strlen(copy) - 1] = 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 && copy[strlen(copy) - 1] == 'e') {
	 copy[strlen(copy) - 1] = 0;
	 if (lookupunit(copy,prefixok)){
	    while (strlen(copy)+1 > bufsize) {
	       growbuffer(&buffer,&bufsize);
	    }
            strcpy(buffer,copy);
	    free(copy);
            return buffer;
	 }
      }
      if (strlen(copy)>2 && copy[strlen(copy) - 1] == 'i') {
	 copy[strlen(copy) - 1] = '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;
      copy += pfxptr->len;
      if (!strlen(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 ERROR        (1<<2)

int
reduceproduct(struct unittype *theunit, int flip)
{

   char *toadd;
   char **product;
   int didsomething = NOREDUCTION;
   struct unittype newunit;
   int ret;

   if (flip)
      product = theunit->denominator;
   else
      product = theunit->numerator;

   for (; *product; product++) {
      for (;;) {
	 if (!strlen(*product))
	    break;
	 toadd = lookupunit(*product,1);
	 if (!toadd) {
            if (!irreducible)
	      irreducible = dupstr(*product);
	    return ERROR;
         }
	 if (strchr(toadd, PRIMITIVECHAR))
	    break;
	 didsomething = DIDREDUCTION;
	 if (*product != NULLUNIT) {
	    free(*product);
	    *product = NULLUNIT;
	 }
         if (parseunit(&newunit, toadd, 0, 0))
	    return ERROR;
         if (flip) ret=divunit(theunit,&newunit);
         else ret=multunit(theunit,&newunit);
         freeunit(&newunit);
         if (ret) 
            return ERROR;
      }
   }
   return didsomething;
}


/*
   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 & ERROR))
	ret |= reduceproduct(theunit, 1);
      if (ret & ERROR){
         if (irreducible) 
	   return E_UNKNOWNUNIT;
         else
	   return E_REDUCE;
      }
   }
   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;
}

/*
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');
}
*/


/* 
   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 */
  for(ptr=current;*ptr;ptr++){    /* If *current is NULL then the unit is  */
    if (*ptr==NULLUNIT) continue; /* dimensionless and the loop is skipped */
    if (count==n) {
      *(out++)=dupstr(*current);
      current = ptr;
      count=0;
    }
    if (!strcmp(*current,*ptr)) count++;
    else return E_NOTROOT;
  }
  if (count==n) *(out++)=dupstr(*current);
  else if (count!=0) return E_NOTROOT;
  *out = 0;
  return 0;
}

int 
rootunit(struct unittype *inunit,int n)
{
   struct unittype outunit;
   int err; 

   initializeunit(&outunit);
   if ((err=completereduce(inunit)))
     return err;
   /* Even numbered root with negative number would be complex */
   if ((n & 1)==0 && 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;
  }
}

/* Raise a unit to a power */

int
unitpower(struct unittype *base, struct unittype *exponent)
{
  double expnum;
  int errcode;
  errcode = unit2num(exponent);
  if (errcode) 
    return errcode;
  expnum = exponent->factor;
  if (floor(expnum)==expnum){ /* integer case */
    errcode = expunit(base,abs((int)expnum));
    if (errcode)
      return errcode;
    if (expnum<0)
      invertunit(base);
  } else if (1.0/fabs(expnum)-floor(1.0/fabs(expnum))<DBL_EPSILON) { /* root */
     expnum = 1/expnum;
     errcode = rootunit(base,abs((int)expnum));
     if (errcode)
       return errcode;
     if (expnum<0) 
       invertunit(base);
  } else {  /* general case */
     errcode = unit2num(base);
     if (errcode) 
       return errcode;
     base->factor = pow(base->factor,expnum);
  }
  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 */

int
evalfunc(struct unittype *theunit, struct func *infunc, int inverse)
{
   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_BADTABLE;
     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_BADTABLE;
       err = completereduce(&result);
       if (err)
	 return E_BADTABLE;
       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)
       return E_NOTINDOMAIN;
     if (thefunc->domain_min && 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==E_PARSEMEM) return err;
     if (err)
       return E_FUNARGDEF;
   }
   freeunit(theunit);
   initializeunit(theunit);
   multunit(theunit, &result);
   return 0;
}


/* 
   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)
{
  unitstr = removepadding(unitstr);
  printf("%s",deftext);
  unitstr = lookupunit(unitstr,1);
  while(unitstr && strspn(unitstr,digits) != strlen(unitstr) && 
	!strchr(unitstr,PRIMITIVECHAR)) {
    tightprint(stdout,unitstr);
    fputs(" = ",stdout);
    unitstr=lookupunit(unitstr,1);
  } 
  showunit(theunit);
  putchar('\n');
}

void
showfuncdefinition(struct func *fun)
{
  int i;

  if (fun->table){  /* It's a table */
    printf("%sinterpolated table with points\n",deftext);
    for(i=0;i<fun->tablelen;i++){
      if (flags.verbose>0)
        printf("\t\t    ");
      printf("%s(", fun->name);
      printf(num_format.format, fun->table[i].location);
      printf(") = ");
      printf(num_format.format, fun->table[i].value);
      if (strchr(digits,fun->tableunit[0]))
        printf(" *");
      printf(" %s\n",fun->tableunit);
    }
    return;
  }
  printf("%s%s(%s) = %s\n", deftext, fun->name, fun->forward.param,
	 fun->forward.def);
    
}


void
showunitlistdef(struct wantalias *alias)
{
  printf("%sunit list, ",deftext);
  tightprint(stdout,alias->definition);
  fputc('\n',stdout);
}


/* Show conversion to a function.  Input unit 'have' is completely reduced. */

int
showfunc(char *havestr, struct unittype *have, struct func *fun)
{
   int err;
   char *dimen;

   err = evalfunc(have, fun, 1);
   if (!err)
     err = completereduce(have);
   if (err) {
     if (err==E_BADFUNCARG){
       printf("conformability error");
       if (fun->table)
	 dimen = fun->tableunit;
       else if (fun->inverse.dimen)
	 dimen = fun->inverse.dimen;
       else 
	 dimen = 0;
       if (!dimen)
	 putchar('\n');
       else {
	 struct unittype want;
	 
	 if (*dimen==0)
	   dimen = "1";
	 printf(": conversion requires dimensions of '%s'\n",dimen);
	 if (flags.verbose==2) printf("\t%s = ",havestr);
	 else if (flags.verbose==1) putchar('\t');
	 showunit(have);
	 if (flags.verbose==2) printf("\n\t%s = ",dimen);
	 else if (flags.verbose==1) printf("\n\t");
	 else putchar('\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 */
	 putchar('\n');
	 }
     } else if (err==E_NOTINDOMAIN)
       printf("Value '%s' is not in the function's range\n",havestr);
     else
       printf("Function evaluation error (bad function definition)\n");
     return 1;
   }
   if (flags.verbose==2)
     printf("\t%s = %s(", havestr, fun->inverse.param);
   else if (flags.verbose==1) 
     putchar('\t');
   showunit(have);
   if (flags.verbose==2)
     putchar(')');
   putchar('\n');
   return 0;
}

/* Print the conformability error message */

void
showconformabilityerr(char *havestr,struct unittype *have,
                      char *wantstr,struct unittype *want)
{
  printf("conformability error\n");
  if (flags.verbose==2)
    printf("\t%s = ",havestr);
  else if (flags.verbose==1)
    putchar('\t');
  showunit(have);
  if (flags.verbose==2)
    printf("\n\t%s = ",wantstr);
  else if (flags.verbose==1)
    printf("\n\t");
  else
    putchar('\n');
  showunit(want);
  putchar('\n');
} /* end showconformabilityerr */



/* see if character is part of a valid decimal number */

int
isdecimal(char c)
{
  if (strchr(digits, c))
    return 1;
  else
    return 0;
}

/*
  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 (strlen(nonum) > 0) {
	    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 *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, "%a", 2.0);
    if (!strncmp(testbuf,two,strlen(two)+1)) 
      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(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 (!*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 displayed precision; indicate in hasnondigits
   if a number will contain any character other than the digits 0-9 in
   the current display format. */

double
round_to_displayed(double value, int *hasnondigits)
{
  int buflen;
  char *buf;
  double rounded;  

  if (!isfinite(value)){
    if (hasnondigits)
      *hasnondigits = 1;
    return value;
  }
  
  /* 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;
}


/* 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 */
  double rnd_value;	/* value rounded to displayed precision */

  rnd_value = round_to_displayed(value, &hasnondigits);

  if (printnum && !(rnd_value == 1 && isdecimal(*unitstr)))
    printf(num_format.format, value);

  if (strpbrk(unitstr, "+-"))   /* show sums and differences of units */
    printf(" (%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
         && strncmp("1|", unitstr, 2) == 0 && isfract(unitstr)
	 && rnd_value != 1 && !hasnondigits)
    fputs(unitstr+1, stdout);
     /* 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 (rnd_value ==  1 && isdecimal(*unitstr))
      fputs(unitstr, stdout);
     /* unitstr begins with a non-fraction number and multiplier was */
     /* shown--prefix a spaced asterisk */
  else if (isdecimal(unitstr[0]))
    printf(" * %s", unitstr);
  else
    printf(" %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;
   havestr = removepadding(havestr);
   wantstr = removepadding(wantstr);
   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)
	  putchar('\t');
        printf("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) 
     printf("\t%s%s%s = ",left,havestr,right);
   else if (flags.verbose==1)
     printf("\t* ");
   if (flags.verbose==2)
     showunitname(have->factor / want->factor, wantstr, PRINTNUM);
   else
     printf(num_format.format, have->factor / want->factor);

   /* Print the second line of output. */

   if (!flags.oneline){
     if (flags.verbose==2) 
       printf("\n\t%s%s%s = (1 / ",left,havestr,right);
     else if (flags.verbose==1)
       printf("\n\t/ ");
     else 
       putchar('\n');
     printf(num_format.format, want->factor / have->factor);
     if (flags.verbose==2) {
       putchar(')');
       showunitname(0,wantstr, NOPRINTNUM); 
     }
   }
   putchar('\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;
  int err, i;
  double direction;

  if (verbose)
    printf("doing function '%s'\n", infunc->name);
  if (infunc->table){         /* Check for monotonicity which is needed for */
    if (infunc->tablelen<=1){ /* unique inverses */
      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 %.8g\n",
	       infunc->name, infunc->table[i].location);
	return;
      }
    return;
  }
  if (infunc->forward.dimen){
    err = parseunit(&theunit, infunc->forward.dimen, 0, 0);
    if (err){
      printf("Function '%s' has invalid type '%s'\n", 
	     infunc->name, infunc->forward.dimen);
      return;
    }
  } else {   /* Try with arbitrary unit as input */
    err = parseunit(&theunit, "kg^3", 0, 0);
    if (err)
      initializeunit(&theunit);  /* Arbitrary unit didn't exist; use scalar */
  }
  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 */
  unitcopy(&saveunit, &theunit);
  err = evalfunc(&theunit, infunc, 0);
  if (err) {
    printf("Error in definition %s(%s) as '%s'\n",
	   infunc->name, infunc->forward.param, infunc->forward.def);
    freeunit(&theunit);
    freeunit(&saveunit);
    return;
  }
  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, 1);
  if (err){
    printf("Error in inverse ~%s(%s) as '%s'\n",
	   infunc->name,infunc->inverse.param, infunc->inverse.def);
    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(3);
      }
    }
    (*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);
}



/* Ideally this would return the actual screen height, but it's so 
   hard to code that portably... */

int 
screensize()
{
   return 20;
}


/* 
   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(funcptr=firstfunc;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);

  outfile = 0;
  if (count==0)
    printf("No matching units found.\n");
#ifdef SIGPIPE
  signal(SIGPIPE, SIG_IGN);
#endif
  if (count>screensize()){
    outfile = popen(pager, "w");
  }
  if (!outfile)
    outfile = stdout;
  for(i=0;i<count;i++){
    fputs(list[i].name,outfile);
    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
}


/* print usage message */

void 
usage()
{
   printf("Usage: %s [options] ['from-unit' 'to-unit']\n",progname);
   fprintf(stdout, "\
\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\
    -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\
    -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\
    -V, --version        show version number and exit\n\n\
To learn about the available units look in %s\n\n\
Report bugs to adrianm@gnu.org.\n\n",
DEFAULTPRECISION,DEFAULTTYPE,DEFAULTPRECISION,UNITSFILE);
   exit(0);
}

/* Print message about how to get help */

void 
helpmsg()
{
  fprintf(stderr,"Try '%s --help' for more information.\n",progname);
  exit(3);
}


/* 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. 
*/

#ifdef READLINE

void
getuser(char **buffer, int *bufsize, char *query)
{
#ifdef SUPPORT_UTF8
  int valid = 0;
  while (!valid){
    if (*buffer) free(*buffer);
    *buffer = readline(flags.quiet?"":query);
    valid = !*buffer || strwidth(*buffer)>=0;
    if (!valid)
      printf("Error: %s\n",invalid_utf8);
  }
#else
    if (*buffer) free(*buffer);
    *buffer = readline(flags.quiet?"":query);
#endif
  if (*buffer && **buffer) add_history(*buffer);
  if (!*buffer){
    if (!flags.quiet)
       putchar('\n');
    exit(0);
  }
}

/* Note:  What should this do?  

   Complete either to the end of a prefix or complete to the 
   end of a unit.  Or if there is a full prefix plus part of a unit, 
   and if that prefix is longer than one character
   then complete that compound.  Don't complete a prefix fragment into
   prefix plus anything.  

   Seems to be working right now...but one problem.  Completing a blank
   entry always gets the cent sign.  Why is that?  My code seems to be
   working ok....

   One thing still needs to be done:  it won't complete a fragment into
   a whole prefix.  It should be possible to type "myr<tab>" and
   get "myria" for example.  
*/
   
char *
completeunits(char *text, int state)
{
  static struct prefixlist *curprefix;
  static struct unitlist *curunit;
  static int uhash;
  static int checkfunctions;
  static struct func *nextfunc;
  char *output,*thistry;
  
  if (!state){     /* state = 0 means this is the first call, so initialize */
    checkfunctions=1;
    nextfunc=firstfunc;
    uhash = 0;
    curprefix=0;
    curunit=utab[uhash];
  }
  output = 0;
  if (!nextfunc)
    checkfunctions = 0;
  while (!output){
    if (checkfunctions){
      if (nextfunc){
	if (strlen(text)<=strlen(nextfunc->name) &&
	    !strncmp(text,nextfunc->name,strlen(text))){
	  output = dupstr(nextfunc->name);
	}
	nextfunc = nextfunc->next;
	continue;
      } else checkfunctions = 0;
    }
    while (!curunit && uhash<HASHSIZE-1){
      uhash++;
      curunit = utab[uhash];
    }
    if (!curunit) return 0;
    thistry = text;
    if (curprefix)
       thistry+=curprefix->len;
    if (strlen(thistry)<=strlen(curunit->name) && 
           !strncmp(curunit->name,thistry,strlen(thistry))){
       output = (char *)mymalloc(1 + strlen(curunit->name)
                        + (curprefix ? curprefix->len:0),"(completeunits)");
       strcpy(output,curprefix?curprefix->name:"");
       strcat(output,curunit->name);
    }
    curunit = curunit->next;
    while (!curunit && uhash<HASHSIZE-1){
      uhash++;
      curunit = utab[uhash];
    }
    if (!curunit && !curprefix){
      if ((curprefix = plookup(text))){
        if (strlen(curprefix->name)>1 && strlen(curprefix->name)<strlen(text)){
          uhash = 0;
          curunit = utab[uhash];
        } else curprefix=0;
      }
    }
  }    
  return output;
}

#else /* We aren't using READLINE */

void
getuser(char **buffer, int *bufsize, char *query)
{
#ifdef SUPPORT_UTF8
  int valid = 0;
  while(!valid){
    if (!flags.quiet) fputs(query, stdout);
    if (!fgetslong(buffer, bufsize, stdin,0)){
      if (!flags.quiet)
         putchar('\n');
      exit(0);
    }
    valid = strwidth(*buffer)>=0;
    if (!valid)
      printf("Error: s\n",invalid_utf8);
  }
#else
  if (!flags.quiet) fputs(query, stdout);
  if (!fgetslong(buffer, bufsize, stdin,0)){
    if (!flags.quiet)
       putchar('\n');
    exit(0);
  }
#endif
}  


#endif /* READLINE */


char *
findunitsfile()
{
  FILE *testfile;
  char *file;
  
  file = getenv("UNITSFILE");
  if (file && *file)
    return file;
  file = UNITSFILE;
  testfile = fopen(file, "rt");
  if (!testfile) {
    char *direc, *env;
    char separator[2];
    char *filename;

    env = getenv("PATH");
    if (env) {
      filename = mymalloc(strlen(env)+strlen(UNITSFILE)+2,"(findunitsfile)");

      if (strchr(env, ';'))	/* MS-DOS */
	strcpy(separator, ";");
      else		        /* UNIX */
	strcpy(separator, ":");
      direc = strtok(env, separator);
      while (direc) {
	strcpy(filename, "");      /* Clear string for next iteration */
	strcat(filename, direc);
	strcat(filename, "/");
	strcat(filename, UNITSFILE);
	testfile = fopen(filename, "rt");
	if (testfile){
	  file = dupstr(filename);
	  break;
	}
	direc = strtok(NULL, separator);
      }
      free(filename);
    }
  }
  if (testfile)
    fclose(testfile);
  return file;
}


char *
personalunitsfile(int skipcheck)
{
  FILE *testfile=0;
  char *homedir, *filename;

  filename = getenv("MYUNITSFILE");
  if (filename && *filename){
    if (skipcheck) 
      return filename;
    testfile = fopen(filename, "rt");
    if (testfile){
	fclose(testfile);
	return filename;
    }
    fprintf(stderr, "%s: cannot open units file (%s) specified in MYUNITSFILE environment variable\n", progname, filename);
    return 0;
  }
  /*
      assume that if HOME is set on Windows, it's meant to override
      HOMEDRIVE and HOMEPATH
  */
  homedir = getenv("HOME");
#ifdef _WIN32
  if (!homedir) {
    char *homedrive, *homepath;
    if ((homedrive = getenv("HOMEDRIVE")) && (homepath = getenv("HOMEPATH"))) {
	filename = mymalloc(strlen(homedrive)+strlen(homepath)
			   +strlen(homeunitsfile)+2,"(personalunitsfile)");
	strcpy(filename,homedrive);
	strcat(filename,homepath);
    }
    else
      return 0;
  }
#else
  if (!homedir)
    return 0;
#endif
  else {
    filename = mymalloc(strlen(homedir)+strlen(homeunitsfile)+2,
                                                      "(personalunitsfile)");
    strcpy(filename,homedir);
  }
  strcat(filename,"/");
  strcat(filename,homeunitsfile);
  if (skipcheck)
    return filename;
  testfile = fopen(filename, "rt");
  if (testfile){
    fclose(testfile);
    return filename;
  }
  free(filename);
  return 0;
}


void
printversion()
{                                           /* coverity[leaked_storage] */
  printf("GNU Units version %s\n%s, %s, locale %s,\nUnits database in %s\n\
Personal units file in %s\n\n\
Copyright (C) 2012 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.\n\n", 
        VERSION, RVERSTR,UTF8VERSTR,mylocale,UNITSFILE,personalunitsfile(1));
}


char *shortoptions = "VvqechSstf:o:d:mnpr1l:";

struct option longoptions[] = {
  {"version", no_argument, 0, 'V'},
  {"quiet", no_argument, &flags.quiet, 1},
  {"silent", no_argument, &flags.quiet, 1},
  {"exponential", no_argument, 0, 'e'},
  {"digits", required_argument, 0, 'd'},
  {"output-format", required_argument, 0, 'o'},
  {"check", no_argument, &flags.unitcheck, 1},
  {"check-verbose", no_argument, &flags.unitcheck, 2},
  {"verbose-check", no_argument, &flags.unitcheck, 2},
  {"verbose", no_argument, &flags.verbose, 2},
  {"file", required_argument, 0, 'f'},
  {"help",no_argument,0,'h'},
  {"strict",no_argument,&flags.strictconvert, 1},
  {"terse",no_argument, 0, 't'},
  {"round",no_argument, 0, 'r'},
  {"compact", no_argument, &flags.verbose, 0},
  {"minus", no_argument, &parserflags.minusminus, 1},
  {"nolists", no_argument, 0, 'n'},
  {"show-factor", no_argument, 0, 'S'},
  {"product", no_argument, &parserflags.minusminus, 0},
  {"one-line", no_argument, &flags.oneline, 1},
  {"oldstar", no_argument, &parserflags.oldstar, 1},
  {"newstar", no_argument, &parserflags.oldstar, 0},
  {"locale", required_argument, 0, 'l'}, 
  {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)
{
   extern char *optarg;
   extern int optind;
   int optchar, optindex;
   int ind;

   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(3);
	    else
		num_format.format = NULL; /* ignore anything given with 'o' option */
	    break;
	 case 'e':
	    num_format.format = NULL;	/* ignore anything given with 'o' option */
	    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(3);
	    }
            if (optarg && *optarg)
	      unitsfiles[ind] = optarg;
	    else 
	      unitsfiles[ind] = findunitsfile();
	    unitsfiles[ind+1] = 0;
	    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 'V':
 	    printversion();
	    exit(3);
         case 0: break;  /* This is reached if a long option is 
                            processed with no return value set. */
         case '?':
	 case 'h':
	    usage();
	    break;
         default:
            helpmsg();
      } 
   }

   if (flags.unitcheck) {
     if (optind != argc){
       fprintf(stderr, "Too many arguments (arguments are not allowed with -c).\n");
       helpmsg();
     }
   } else {
     if (optind == argc - 2) {
        flags.quiet=1;
        *from = argv[optind];
        *to = dupstr(argv[optind+1]);  /* This string may need to get rewritten later */
        return 0;                      /* and we want the buffer to be free()able */
     }

     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();
     }
   }

   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("^");
  }
} /* 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)){
    printf("Unit list not allowed\n");
    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);
    printf("%s",errmsg);
    if (err==E_UNKNOWNUNIT && irreducible)
      printf(" '%s'", irreducible);
    putchar('\n');

    return 1;
  }
  if ((err=completereduce(theunit))){
    printf("%s",errormsg[err]);
    if (err==E_UNKNOWNUNIT)
      printf(" '%s'", irreducible);
    putchar('\n');
    return 1;
  }
  return 0;
}



/*
   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.

   I 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, WHITE)]) {  /* 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;
          flags.verbose = 2;   /* always use verbose form to be unambiguous */
          *(strchr(firstunitstr, UNITSEPCHAR)) = '\0';
          firstunitstr = removepadding(firstunitstr);
          unitstr = removepadding(unitstr);
          showpointer(promptlen);
          showconformabilityerr(firstunitstr, unit, unitstr, unit+1);
          flags.verbose = wasverbose;
	}
        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, strlen(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;
  int i;

  initializeunit(&one);

  /* Check all functions for valid definition and correct inverse */
  
  for(funcptr=firstfunc;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 (parseunit(&have, uptr->name,0,0) 
	  || completereduce(&have) 
	  || compareunits(&have,&one, ignore_primitive)){
	if (isfunction(uptr->name)) 
	  printf("Unit '%s' hidden by function '%s'\n", uptr->name, uptr->name);
	else
	  printf("'%s' defined as '%s' irreducible\n",uptr->name, uptr->value);
      } 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 */ 

  for(i=0;i<PREFIXTABSIZE;i++)
    for(pptr = ptab[i]; pptr; pptr = pptr->next){
      if (verbosecheck)
        printf("doing '%s'\n",pptr->name);
      if (parseunit(&have, pptr->name,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);
    }
}


/*
   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) */
  size_t wantstrlen;
  int firstunit = 1;	/* first unit in a multi-unit string */
  int value_shown = 0;	/* has a value been shown? */

  initializeunit(&want);
  remainder = have->factor;
  lastunitstr = 0;
  nextunitstr = 0;
  round_dir = 0;

  wantstrlen = strlen(wantstr);

  if (flags.round) {
    /* disable unit repetition with terminal UNITSEPCHAR when rounding */
    if (wantstr[wantstrlen - 1] == UNITSEPCHAR)
      wantstr[wantstrlen - 1] = '\0';
    if ((lastwantstr = strrchr(wantstr, UNITSEPCHAR)))
      lastwantstr++;
  }

  while (wantstr) {
    if ((nextunitstr = strchr(wantstr, UNITSEPCHAR)))
      *(nextunitstr++) = '\0';
    wantstr = removepadding(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 (!*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 (lastwantstr && *lastwantstr) {	/* more than one unit */
	  lastwantstr = removepadding(lastwantstr);
	  initializeunit(&lastwant);
	  if (processunit(&lastwant, lastwantstr, NOPOINT)) {
	    freeunit(&lastwant);
	    return 1;
	  }
	  remainder = floor(remainder / lastwant.factor + 0.5) * lastwant.factor;
	}
	else	/* first unit is last unit */
	  remainder = floor(remainder / want.factor + 0.5) * want.factor;

	round_dir = remainder - value;
      }
      if (flags.verbose == 2) {
        havestr = removepadding(havestr);
        printf("\t%s = ", havestr);
      } else if (flags.verbose == 1)
        putchar('\t');
    } /* end if first unit */

    if (nextunitstr) {
      remainder = want.factor * modf(remainder / want.factor, &value);
      if (round_to_displayed(remainder / want.factor, NULL) == 1) {
	value++;               /* at displayed precision, value rounds up */
	remainder = 0;         /* and remainder is zero */
      }
      else if (fabs(remainder / have->factor) < DBL_EPSILON)
	remainder = 0;              /* remainder is just rounding error */
    }
    else {	/* last unit */
      value = remainder / want.factor;
      if (!flags.round) {    /* round to displayed precision */
	value = round_to_displayed(value, NULL);
      }
    }
    if (!flags.verbose){
      if (!firstunit) 
	putchar(UNITSEPCHAR);
      printf(num_format.format,value);
      value_shown=1;
    } else { /* verbose case */
      if (value != 0) {
        if (value_shown) /* have already displayed a number so need a '+' */
          fputs(" + ", stdout);
	showunitname(value, wantstr, PRINTNUM);
      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 */
    fputs("0 ", stdout);
    if (isdecimal(*lastunitstr))
      fputs("* ", stdout);
    fputs(lastunitstr, stdout);
  }

  if (round_dir != 0) {
    if (flags.verbose){
      if (round_dir > 0)
	printf(" (rounded up to nearest %s) ", lastunitstr);
      else
	printf(" (rounded down to nearest %s) ", lastunitstr);
    } else 
      printf("%c%c", UNITSEPCHAR, round_dir > 0 ?'-':'+');	
  }
  putchar('\n');
  return 0;
} /* end showunitlist */


/* 
   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;
  
  str=removepadding(str);
  if (have && !strcmp(str, UNITMATCH)){
    tryallunits(have,0);
    return 1;
  }
  if (!strncmp(SEARCHCOMMAND,str,strlen(SEARCHCOMMAND))){
    str+=strlen(SEARCHCOMMAND);
    if (!strchr(WHITE,*str))
      return 0;
    str = removepadding(str);
    if (strlen(str)==0){
      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 (!strncmp(HELPCOMMAND,str,strlen(HELPCOMMAND))){
    str+=strlen(HELPCOMMAND);
    if (!strchr(WHITE,*str))
      return 0;
    str = removepadding(str);
    if (strlen(str)==0){
      printf("\n\
Units 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 units type Ctrl-%s or Ctrl-C.       %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",
queryhave,
queryhave, querywant,
querywant,
queryhave,querywant,queryhave,querywant,
EOFCHAR,
queryhave,querywant,
querywant,
UNITMATCH,
queryhave,querywant);
      return 1;
    }
    if ((function = isfunction(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;
    }
#ifdef _MKS_TOOLKIT
    if (!strcmp(pager, "more") || !strcmp(pager, "less"))
      sprintf(commandbuf,"%s +%dg %s", pager, unitline, file);
    else
#endif
    sprintf(commandbuf,"%s +%d %s", pager, unitline, file);
    if (system(commandbuf))
      fprintf(stderr,"%s: unable to 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;
#ifndef NO_SETLOCALE  
  temp = setlocale(LC_CTYPE,"");
#endif
  if (!temp)
    temp = getenv("LC_CTYPE");
  if (!temp)
    temp = getenv("LANG");
  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;

#ifndef READLINE
  if (buflen) usefree = 0;
#endif

  if (*string && **string) {   /* check that string is defined and nonempty */
    input = removepadding(*string);
    if ((aliasptr=aliaslookup(input))){
      if (checkunitlist(aliasptr->definition,NOERRMSG)){
	fputs("Unit list definition contains errors.\n",stdout);
	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;
}
  

/* 
   the basename function removes the leading path components and a .exe 
   extension from the input string 
*/

#define  isexe(s) ((strlen(s) == 4) && (tolower(s[1]) == 'e') \
		 && (tolower(s[2]) == 'x') && (tolower(s[3]) == 'e'))
char
*basename(char * path)
{
    char *p;
    if ((p = strrchr(path,'\\')) || (p = strrchr(path, '/')))
	path = p + 1;
    if ((p = strrchr(path, '.')) && isexe(p))
	*p = '\0';
    return(path);
}


/* 
   Remaps the locale name for Windows systems that return weird
   values for the locale. 
*/


void
remaplocale()
{
  FILE *map;
  char *value;
  char name[80];
  map = fopen(LOCALEMAP,"rt");
  if (!map)
    fprintf(stderr,"%s: Unable to read locale map '%s'\n",progname,LOCALEMAP);
  else {
    while(!feof(map)){
      if (!fgets(name,80,map))
	break;
      value=strchr(name,'#');
      if (value) *value=0;
      value=strchr(name,'\t');
      if (!value) continue;
      *value++=0;
      value = removepadding(value);
      if (!strcmp(removepadding(name), mylocale))
	mylocale = dupstr(value);
    }
    fclose(map);
  }
}


int
main(int argc, char **argv)
{
   static struct unittype have, want;
   char *havestr=0, *wantstr=0;
   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 interactive;
   int readerr;
   char **unitfileptr;
   int unitcount=0, prefixcount=0, funccount=0;   /* for counting units */
   int queryhavewidth = strwidth(queryhave);
   int querywantwidth = strwidth(querywant);

   /* 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.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 = basename(argv[0]);

   checklocale();

#ifdef _WIN32
   if (!strcmp(homeunitsfile,".units"))
     homeunitsfile = "unitdef.units";
   remaplocale();
#endif

#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+-*/()|^;";
#endif

   unitsfiles[0] = 0;
   interactive = processargs(argc, argv, &havestr, &wantstr);
  
   /* user has specified the complete format--use it */
   if (num_format.format != NULL) {
       if (parsenumformat())
	 exit(1);
   }
   else
       setnumformat();

   if (flags.verbose==0)
     deftext = "";

   if (!unitsfiles[0]){
     unitsfiles[0] = findunitsfile();
     unitsfiles[1] = personalunitsfile(0);
     unitsfiles[2] = 0;
   }

   for(unitfileptr=unitsfiles;*unitfileptr;unitfileptr++){      
     readerr = readunits(*unitfileptr, stderr, &unitcount, &prefixcount, 
			 &funccount, 0);
     if (readerr==E_MEMORY) 
       exit(3);
     if (readerr==E_FILE){
       fprintf(stderr, "%s: unable to open units file '%s'.  ",
	       progname, *unitfileptr);
       perror(0);
       exit(1);
     }
   }

   if (flags.quiet){
     queryhavewidth = 0;          /* No prompts are being printed */
     querywantwidth = 0;
   } else
     printf("%d units, %d prefixes, %d nonlinear units\n\n", unitcount, prefixcount,
     funccount);
   if (flags.unitcheck) {
      checkunits(flags.unitcheck==2 || flags.verbose==2);
      exit(0);
   }

   if (!interactive) {
#ifdef SUPPORT_UTF8
      if (strwidth(havestr)<0){
        printf("Error: %s on input\n",invalid_utf8);
        exit(1);
      }
      if (wantstr && strwidth(wantstr)<0){
        printf("Error: %s on input\n",invalid_utf8);
        exit(1);
      }
#endif
      replace_minus(havestr);
      if (wantstr) replace_minus(wantstr);
      if ((funcval = isfunction(havestr))){
	showfuncdefinition(funcval);
  	exit(0);
      }
      if ((alias = aliaslookup(removepadding(havestr)))){
	showunitlistdef(alias);
	exit(0);
      }
      if (processunit(&have, havestr, NOPOINT))
	 exit(1);
      if (!wantstr){
         showdefinition(havestr,&have);
         exit(0);
      }
      if (replacealias(&wantstr, 0)) /* the 0 says that we can free wantstr */
         exit(1);
      if ((funcval = isfunction(wantstr))){
         if (showfunc(havestr, &have, funcval))
	   exit(1);
	 else
	   exit(0);
      }
      if (processwant(&want, wantstr, NOPOINT))
	 exit(1);
      if (strchr(wantstr, UNITSEPCHAR)){
        if (showunitlist(havestr, &have, wantstr))
          exit(1);
        else
          exit(0);
      }
      if (showanswer(havestr,&have,wantstr,&want))
	 exit(1);
      else
	 exit(0);
   } else {
      pager = getenv("PAGER");
      if (!pager)
	pager = DEFAULTPAGER;
      for (;;) {
	 do {
  	    fflush(stdout);
            getuser(&havestr,&havestrsize,queryhave);
	    replace_minus(havestr);
	 } while (isblankstr(havestr) || ishelpquery(havestr,0) ||
		  (!isfunction(havestr) 
		   && !aliaslookup(removepadding(havestr))
		   && processunit(&have, havestr, queryhavewidth)));
	 if ((alias = aliaslookup(removepadding(havestr)))){
	   showunitlistdef(alias);
	   continue;
	 }
         if ((funcval = isfunction(havestr))){
	   showfuncdefinition(funcval);
	   continue;
	 }
	 do { 
	   int repeat; 
	   do {
	     repeat = 0;
             fflush(stdout);
	     getuser(&wantstr,&wantstrsize,querywant);
	     replace_minus(wantstr);
             if (ishelpquery(wantstr, &have)){
	       repeat = 1;
	       printf("%s%s\n",queryhave, havestr);
	     }
	   } while (repeat);
	 } while (replacealias(&wantstr, &wantstrsize)
                  || (isfunction(wantstr)==0
  		      && processwant(&want, wantstr, querywantwidth)));
         if (isblankstr(wantstr))
           showdefinition(havestr,&have);
         else if (strchr(wantstr, UNITSEPCHAR))
           showunitlist(havestr, &have, wantstr);
         else if ((funcval = isfunction(wantstr)))
           showfunc(havestr, &have, funcval);
	 else {
           showanswer(havestr,&have,wantstr, &want);
	   freeunit(&want);
	 }
         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?  

method of reporting the definition of a function or table:
  Is it ok as is?  Report inverses?

functions allocated in linked list: could be bad if lots of functions

completion that recognizes built in functions (?)

Way to report the reduced form of the two operands of a sum when
they are not conformable.

*/

