/*++++++++++++++
.IDENTIFICATION units.c
.LANGUAGE       C
.AUTHOR         Francois Ochsenbein [CDS]
.ENVIRONMENT    Unix, CDS Catalogues
.KEYWORDS       Table, FITS
.VERSION  1.0   15-Jul-1993: C-shell version(mods 1.1 to 1.9 deleted)
.VERSION  2.0   02-Sep-1994: C package
.VERSION  2.1   01-Jun-1995: Added "day" "month"
.VERSION  2.2   03-Oct-1995: Added function unit_si
.VERSION  2.3   21-Nov-1995: *** ERROR in definitions of lm  lx ****
.VERSION  2.4   04-Jun-1996: Sort output with ? option
.VERSION  2.5   12-Apr-1997: Allow "[" (log scale) arount units.
.VERSION  2.6   14-Jan-1998: Allow "m:s" (time)
.VERSION  3.0   24-Jan-1999: Allow [ ] for log
.VERSION  3.1   10-Jun-2000: In dates, use "yy" for dates after 2000.
.VERSION  3.2   23-Jul-2000: Accept s^2 for square seconds
.VERSION  3.3   09-Apr-2003: Accept "date" in unit_get
.VERSION  3.4   26-Mar-2004: Addition "h:m" "Y-M-D:h:m:s"
.VERSION  3.5   07-Apr-2004: ALARM if case of loop
.VERSION  3.6   28-Nov-2004: added unit_dim
.VERSION  3.7   17-Mar-2005: Bug in datime_pic corrected
.VERSION  3.8   05-May-2005: Accept Bohr radius
.VERSION  3.9   19-Sep-2005: 2-digit year ==> interval [1938, 2037]
.VERSION  3.91  03-Jun-2006: Accept YYYY.MM.DD.DD (fractions)
.VERSION  4.0   09-Aug-2006: Units with offset (degC, etc)
.VERSION  4.01  27-Sep-2006: Accept "**" for exponentiation
.VERSION  4.02  02-Jun-2007: Cosmetics (-Wall)
.VERSION  4.03  28-Oct-2007: Problem with decimal days...
.VERSION  4.04  09-Jan-2008: Added Crab
.VERSION  4.05  26-Aug-2008: unitex in softex
.VERSION  4.06  27-Sep-2008: pb "datime"
.VERSION  4.07  08-May-2009: Msun Lsun etc (shorter units)
.VERSION  4.08  02-Jun-2009: datime
.VERSION  4.09  08-Sep-2009: Added Angstroem
.VERSION  4.10  15-Dec-2009: 64-bit
.VERSION  4.11  24-Sep-2010: utf-8 for deg Angstrom mu
.VERSION  4.2   28-Sep-2010: unit_freq unit_wave
.VERSION  4.21  03-Jan-2012: units Rgeo+Rjup
.VERSION  4.22  07-Jun-2012: ... but bad values for Mjup !
.VERSION  4.23  04-Dec-2012: Pb with a0 and µ0
			TODO: other Unicode units ?
.COMMENTS       Dealing with interpretaion of Units according to
	the "Adopted Standards for Astronomical Catalogues"
	(http://vizier.u-strasbg.fr/doc/catstd.htx)
	The recursice definition is as follows:
	Full_Unit = factor Complex_unit
	Complex_Unit = single_Unit
		     | single_Unit OP single_Unit
		     | "[" single_Unit OP single_Unit "]"
	single_Unit  = extended_UnitSymbol
		     | extended_UnitSymbol power
	extended_UnitSymbol = UnitSymbol
			    | Magnitude_Prefix UnitSymbol
			    | "(" Full_Unit ")"		###if UNITp###
	power 	= Number
		| +Number
		| -Number
	OP	= . | /
	UnitSymbol = result of ufind
---------------*/

#define VERSION "4.23 (2012-12-04)"

#ifndef UNITp
#define UNITp 1
#endif

#ifndef DEBUG
#define DEBUG 0
#endif

#ifdef MAIN			/* MAIN Program = standalone = TEST	*/
#define TEST 1
#endif

#ifdef TEST			/* TEST for the MAIN program		*/
#define MAXUNIT	 512
#define UNITp 	   1		/* Allow parentheses in expressions	*/
#endif

#ifndef MAXUNIT
#define MAXUNIT		80	/* Max length of expanded Unit        	*/
#endif

#define MAXEXU		40	/* Max length of a unit explanation   	*/

#include <units.h>		/* isalpha.. */
#include <stdio.h>
#include <stdlib.h>		/* for qsort */
#include <unistd.h>		/* for sleep */
#include <string.h>		/* for qsort */
#include <ctype.h>		/* isalpha.. */
#include <math.h>		/* M_PI */
#include <time.h>		/* Date */
#define MJD1970          40587	/* MJD date of 01-Jan-1970 UT */

#define _c_	299792458.	/* Speed of light, m/s */
#define _hbar_	1.0545716e-34	/* hbar = h/2{pi} */
#define _h_    (2.*M_PI*_hbar_)	/* Planck	  */
typedef int (*SORT_FCT)(/* const void *, const void * */) ;

static char *mksa[8] = { "kg", "m", "s", "A", "K", "cd", "mol", "mag" };
static char sydim[8] = { 'M' , 'L', 'T', 'A', 'K',  0,    0,     0    };
typedef struct {	/* Special labels */
    char *symbol;
    char *explain;	/* Explanation; starts with '?' for discouraged unit */
    double factor;
    char mksa[8];	/* kg m s A K cd mol mag */
    double log_factor;	/* Factor outside  log   */
    double offset;	/* Non-zero for degC etc */
} UNIT;

#define  _	'0'
static UNIT operator[] = {	/* Allowed operators;
				non-multiplicative have a 0 factor */
    {"2", 	"square ", 	0.0,	},
    {"3", 	"cubic ",	0.0,	},
    {"+", 	"power+",	0.0,	},
    {"-", 	"power-",	0.0,	},
    {"/", 	"per ",		1.0,	{'/'}},
    {".", 	"times ",	1.0,	{'*'}},
    {"*", 	"times ",	1.0,	{'*'}},
};

static UNIT multiple[] = {	/* Symbols for multiples */
    {"mu", 	"micro",	1.e-6,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"da", 	"deca",		1.e+1,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"k", 	"kilo",		1.e+3,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"m", 	"milli",	1.e-3,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"c", 	"centi",	1.e-2,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"u", 	"micro",	1.e-6,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"M", 	"mega",		1.e+6,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"n", 	"nano",		1.e-9,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"G", 	"giga",		1.e+9,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"d", 	"deci",		1.e-1,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"h", 	"hecto",	1.e+2,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"p", 	"pico",		1.e-12,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"T", 	"tera",		1.e+12,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"f", 	"femto(10-15)",	1.e-15,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"P", 	"peta(10+15)",	1.e+15,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"a", 	"atto(10-18)",	1.e-18,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"E", 	"exa(10+18)",	1.e+18,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"z", 	"zepto(10-21)",	1.e-21,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"Z", 	"zetta(10+21)",	1.e+21,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"y", 	"yocto(10-24)",	1.e-24,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"Y", 	"yotta(10+24)",	1.e+24,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
    {"µ", 	"micro",	1.e-6,	{_+0, _+0, _+0, _+0, _+0, _,_,_}},
};

static UNIT unit[] = {	/* ORDER is IMPORTANT ! First found is kept... */
				/*     factor    kg   m    s    A    K  cd m m*/
  {"---", "",    		1.0,	    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"%",	  "percent",		1.e-2,	    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"h",	  "hour ",		3.6e3,	    {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"min", "minute ",		60.,	    {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"s",	  "second ",		1.0,	    {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"mag", "magnitude ",		1.0,	    {_+0, _+0, _+0, _+0, _, _,_,_+1}},
  {"Jy",  "Jansky(10-26W/m2/Hz) ",1.e-26,   {_+1, _+0, _-2, _+0, _+0, _,_,_}},
  {"deg", "degree ",	     (1./360.),	    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"rad", "radian ",	    (0.5/M_PI),	    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"sr",  "steradian ",	   (0.25/M_PI),	    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"arcmin", "minute of arc ",(1./21600.),  {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"arcsec", "second of arc ",(1.e-3/1296.),{_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"arcm",   "minute of arc ",(1./21600.),  {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"arcs",   "second of arc ",(1.e-3/1296.),{_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"mas", "milli-second of arc ",
			      (1.e-6/1296.),{_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"uas", "micro-second of arc ",
	 		      (1.e-9/1296.),{_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"Sun", "Solar unit ",	    1.0,    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"solMass","solar mass ", 1.989e+30,	    {_+1, _+0, _+0, _+0, _+0, _,_,_}},
  {"solRad", "solar radius ", 6.9599e+8,    {_+0, _+1, _+0, _+0, _+0, _,_,_}},
  {"solLum", "solar luminosity ", 3.826e+26,{_+1, _+2, _-3, _+0, _+0, _,_,_}},
  {"geoMass", "Earth mass ",   5.9742e+24,  {_+1, _+0, _+0, _+0, _+0, _,_,_}},
  {"jovMass", "Jupiter mass ", 1.8986e+27,  {_+1, _+0, _+0, _+0, _+0, _,_,_}},
  {"Msun",  "solar mass ",     1.989e+30,   {_+1, _+0, _+0, _+0, _+0, _,_,_}},
  {"Rsun",  "solar radius ",  6.9599e+8,    {_+0, _+1, _+0, _+0, _+0, _,_,_}},
  {"Lsun",  "solar luminosity ", 3.826e+26, {_+1, _+2, _-3, _+0, _+0, _,_,_}},
  {"Mgeo",  "Earth mass ",   5.9742e+24,    {_+1, _+0, _+0, _+0, _+0, _,_,_}},
  {"Rgeo",  "Earth radius (eq)", 6378.1e3,  {_+0, _+1, _+0, _+0, _+0, _,_,_}},
  {"Mjup",  "Jupiter mass ", 1.8986e+27,    {_+1, _+0, _+0, _+0, _+0, _,_,_}},
  {"Rjup", "Jupiter radius (eq)", 71492.e3, {_+0, _+1, _+0, _+0, _+0, _,_,_}},
  {"m",	  "metre ",		1.0,	    {_+0, _+1, _+0, _+0, _+0, _,_,_}},
  {"Hz",  "Herz ",		1.0,	    {_+0, _+0, _-1, _+0, _+0, _,_,_}},
  {"kg",  "kilogram ",		1.0,	    {_+1, _+0, _+0, _+0, _+0, _,_,_}},
  {"g",	  "gram ",		1.e-3,	    {_+1, _+0, _+0, _+0, _+0, _,_,_}},
  {"K",	  "Kelvin ",		1.0,	    {_+0, _+0, _+0, _+0, _+1, _,_,_}},
  {"Pa",  "Pascal ",		1.0,	    {_+1, _-1, _-2, _+0, _+0, _,_,_}},
  {"T",	  "Tesla ",		1.0,	    {_+1, _+0, _-2, _-1, _+0, _,_,_}},
  {"V",	  "Volt ",		1.0,	    {_+1, _+2, _-3, _-1, _+0, _,_,_}},
  {"W",	  "Watt ",		1.0,	    {_+1, _+2, _-3, _+0, _+0, _,_,_}},
  {"J",	  "Joule ",		1.0,	    {_+1, _+2, _-2, _+0, _+0, _,_,_}},
  {"eV",  "electron-Volt ", 1.602177e-19,   {_+1, _+2, _-2, _+0, _+0, _,_,_}},
  {"Ry",  "Rydberg(13.6eV) ", 21.798948e-19,{_+1, _+2, _-2, _+0, _+0, _,_,_}},
  {"yr",  "year ", 	    31.5576e+6,	    {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"a",	  "year ", 	    31.5576e+6,	    {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"d",	  "day ",	       86.4e+3,	    {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"AU",  "astronomical unit ", 1.49598e+11,{_+0, _+1, _+0, _+0, _+0, _,_,_}},
  {"au",  "astronomical unit ", 1.49598e+11,{_+0, _+1, _+0, _+0, _+0, _,_,_}},
  {"pc",  "parsec ",            3.0857e+16, {_+0, _+1, _+0, _+0, _+0, _,_,_}},
  {"al",  "light-year ",       .946053e+16, {_+0, _+1, _+0, _+0, _+0, _,_,_}},
  {"JD",  "Julian Day ",       86.4e+3,     {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"pix", "pixel ",		1.0,	    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"ct",  "count ",		1.0,	    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"ph",  "photon ",		1.0,	    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"A",	  "Ampere ",		1.0,	    {_+0, _+0, _+0, _+1, _+0, _,_,_}},
  {"barn", "barn(10-28m2) ",	1.e-28,	    {_+0, _+2, _+0, _+0, _+0, _,_,_}},
  {"bit", "binary information unit ", 1.0,  {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"byte", "byte(8bits) ",	1.0,	    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"C",	  "Coulomb ",		1.0,	    {_+0, _+0, _+1, _+1, _+0, _,_,_}},
  {"D",	  "Debye (dipole)",   (1.e-29/3.),  {_+0, _+1, _+1, _+1, _+0, _,_,_}},
  {"cd",  "candela(lumen/sr) ",	1.0,	    {_+0, _+0, _+0, _+0, _, _+1,_,_}},
  {"F",	  "Farad ",		1.0,	    {_-1, _-2, _+4, _+2, _+0, _,_,_}},
  {"H",	  "Henry ",		1.0,	    {_+1, _+2, _-2, _-2, _+0, _,_,_}},
  {"lm",  "lumen ",  	  (0.25/M_PI),	    {_+0, _+0, _+0, _+0, _, _+1,_,_}},
  {"lx",  "lux(lm/m2) ",  (0.25/M_PI),	    {_+0, _-2, _+0, _+0, _, _+1,_,_}},
  {"mol", "mole ",		1.0,	    {_+0, _+0, _+0, _+0, _, _,_+1,_}},
  {"N",   "Newton ",		1.0,	    {_+1, _+1, _-2, _+0, _+0, _,_,_}},
  {"Ohm", "Ohm(V/A) ",		1.0,	    {_+1, _+2, _-3, _-2, _+0, _,_,_}},
  {"S",	  "Siemens(A/V) ",	1.0,	    {_-1, _-2, _+3, _+2, _+0, _,_,_}},
  {"Wb",  "Weber(V.s) ",	1.0,	    {_+1, _+2, _-2, _-1, _+0, _,_,_}},
  {"u",	"atomic mass unit ", 1.6605387e-27, {_+1, _+0, _+0, _+0, _+0, _,_,_}},
  {"ppm",  "part per million",	1.e-6,	    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"µas", "micro-second of arc ",
                             (1.e-9/1296.), {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"\"d:m:s\"", "degree arcminute arcsecond (sexagesimal angle from degree)",
	 		      (1./360.),    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"\"h:m:s\"",  "hour minutes seconds (sexagesimal time from hours)",
		 	          3600.0,   {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"\"h:m\"", "hour minutes (sexagesimal time from hours)",
			          3600.0,   {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"\"m:s\"", "minutes seconds (sexagesimal time from minutes)",
			          60.0,     {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"\"HHMMSS\"", "hour minutes seconds (sexagesimal time without separator)",
			         3600.0,    {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"\"HH:MM:SS\"", "hour minutes seconds (sexagesimal time)",
			         3600.0,    {_+0, _+0, _+1, _+0, _+0, _,_,_}},
#if 0
  {"\"Y:M:D\"", 	"Date as Year Month_Number_or_Name Day_Number",
				      86400.,	_+0, _+0, _+1, _+0, _+0, _,_,_},
  {"\"DD/MM/YY\"",	"Day/Month/Year (from 1900)",
				      86400.,	_+0, _+0, _+0, _+0, _+0, _,_,_},
  {"\"DD-MMM-YYYY\"",	"Day-Month_abbreviation-Year",
				      86400.,	_+0, _+0, _+0, _+0, _+0, _,_,_},
  {"\"YYDDD\"",	"Year(from 1900).Day_of_Year",
				      86400.,	_+0, _+0, _+0, _+0, _+0, _,_,_},
  {"\"YY.DDD\"",	"Year(from 1900).Day_of_Year",
				      86400.,	_+0, _+0, _+0, _+0, _+0, _,_,_},
  {"\"YY.MM.DD\"",	"Year(from 1900).Month.Day",
				      86400.,	_+0, _+0, _+0, _+0, _+0, _,_,_},
  {"\"YYYYMMDD\"",	"Year Month Day",
				      86400.,	_+0, _+0, _+0, _+0, _+0, _,_,_},
  {"\"YYMMDD\"",	"Year(from 1900) Month Day",
				      86400.,	_+0, _+0, _+0, _+0, _+0, _,_,_},
#endif
  {"\"date\"", "Fully qualified date",
	 		         86400.,    {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"\"datime\"", "Fully qualified date/time",
		 	         86400.,    {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"\"Y-M-D,h:m:s\"", "Fully qualified date/time",
		 	         86400.,    {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"\"month\"",	"Month name or number",
		 	         86400.,    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"\"MM/YY\"",	"Month/Year(from 1900)",
		 	         86400.,    {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"\"MM/yy\"",	"Month/Year(from 2000)",
		 	         86400.,    {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"\"day\"",	"Day of month number",
		 	         86400.,    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
					/* A few constants... */
  {"pi", "pi(=3.14...)",	   M_PI,    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"c", "c(speed_of_light)",   _c_,         {_+0, _+1, _-1, _+0, _+0, _,_,_}},
  {"G", "G(gravitation constant)",
			      6.673e-11,    {_-1, _+3, _-2, _+0, _+0, _,_,_}},
  {"\\h", "hbar(Planck constant)",
			   1.0545716e-34,   {_+1, _+2, _-1, _+0, _+0, _,_,_}},
  {"e", "e(electron_charge) ",1.602177e-19, {_+0, _+0, _+1, _+1, _+0, _,_,_}},
  {"k", "k(Boltzmann) ",       1.38065e-23, {_+1, _+2, _-2, _+0, _-1, _,_,_}},
  {"R", "R(gas_constant) ",         8.3143, {_+1, _+2, _-2, _+0,_-1,_,_-1,_}},
  {"mp", "mp(proton_mass) ",  1.672661e-27, {_+1, _+0, _+0, _+0, _+0, _,_,_}},
  {"me", "me(electron_mass) ", 9.109382e-31,{_+1, _+0, _+0, _+0, _+0, _,_,_}},
    		/* Bohr radius = 4 \pi \epsilon_0 \hbar^2 / m e^2  */
  {"a0", "(Bohr radius) ", 5.29177208e-11,  {_+0, _+1, _+0, _+0, _+0, _,_,_}},
  {"degree", "degree ",	     (1./360.),	    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"eps0", "(electric constant) ",
		    8.854187817620389e-12,  {_-1, _-3, _+4, _+2, _+0, _,_,_}},
  {"mu0", "(magnetic constant) ",
		             (4.e-7*M_PI),  {_+1, _+1, _-2, _-2, _+0, _,_,_}},
  {"µ0", "(magnetic constant) ",
		             (4.e-7*M_PI),  {_+1, _+1, _-2, _-2, _+0, _,_,_}},
  {"alpha", "(fine structure constant) ",
	 	             (1./137.036),  {_+0, _+0, _-0, _-0, _+0, _,_,_}},
  {"muB", "(Bohr magneton) ",	/* e.hbar/2me */
			     9.274009e-28,  {_+0, _+2, _-0, _+1, _+0, _,_,_}},
    /* Eh = Hartree energy = 2Ry = alpha^2.me.c^2 */
					/* A few obsolste units...	*/
  {"degC", "Celsius ",		    1.0,    {_+0, _+0, _+0, _+0, _+1, _,_,_},
	 				       0., 273.15},
  {"MJD", "Mod. Julian Date (JD-2400000.5) ",
    	 		          86.4e+3,  {_+0, _+0, _+1, _+0, _+0, _,_,_},
	 		       		       0., 86.4e+3*2400000.5},
  {"Crab", "Crab (X-ray) flux ", 
      				1.43e-11,   {_+1, _+0, _-3, _+0, _+0, _,_,_}},
  {"atm", "atmosphere ",      101325.,	    {_+1, _-1, _-2, _+0, _+0, _,_,_}},
  {"mmHg", "?mercury_mm ",     133.3224,    {_+1, _-1, _-2, _+0, _+0, _,_,_}},
  {"l",	"?litre ", 		  1.e-3,    {_+0, _+3, _+0, _+0, _+0, _,_,_}},
  {"hr", "?hour(use 'h') ",	  3.6e3,    {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"sec", "?second (use 's')",	  1.0,      {_+0, _+0, _+1, _+0, _+0, _,_,_}},
  {"inch", "?inch ",		  2.54e-2,  {_+0, _+1, _+0, _+0, _+0, _,_,_}},
  {"t",	"?ton ",		   1.e+3,   {_+1, _+0, _+0, _+0, _+0, _,_,_}},
  {"month", "?month ",		   1.0,     {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"erg", "?erg(10-7J) ",	   1.e-7,   {_+1, _+2, _-2, _+0, _+0, _,_,_}},
  {"dyn", "?dyne(10-5N) ",	   1.e-5,   {_+1, _+1, _-2, _+0, _+0, _,_,_}},
  {"bar", "?bar(10+5Pa) ",	   1.e+5,   {_+1, _-1, _-2, _+0, _+0, _,_,_}},
  {"gauss", "?Gauss(10-4T) ",	   1.e-4,   {_+1, _+0, _-2, _-1, _+0, _,_,_}},
  {"cal", "?calorie ",		   4.1854,  {_+1, _+2, _-2, _+0, _+0, _,_,_}},
  {"Å",   "?Angstroem(0.1nm) ",	   1.e-10,  {_+0, _+1, _+0, _+0, _+0, _,_,_}},
  {"Angstrom","?Angstroem(0.1nm) ",1.e-10,  {_+0, _+1, _+0, _+0, _+0, _,_,_}},
  {"Angstroem","?Angstroem(0.1nm) ",1.e-10,  {_+0, _+1, _+0, _+0, _+0, _,_,_}},
  {"lyr", "?light-year (c*yr) ",
	 		      .946053e+16,  {_+0, _+1, _+0, _+0, _+0, _,_,_}},
  {"degF", "?Fahrenheit ",	 (5./9.),   {_+0, _+0, _+0, _+0, _+1, _,_,_},
	 				       0., (273.15-(32.*5./9.))},
  {"°", "degree ",	     (1./360.),	    {_+0, _+0, _+0, _+0, _+0, _,_,_}},
  {"°C", "Celsius ",		    1.0,    {_+0, _+0, _+0, _+0, _+1, _,_,_},
	 				       0., 273.15},
  {"°F", "?Fahrenheit ",	 (5./9.),   {_+0, _+0, _+0, _+0, _+1, _,_,_},
	 				       0., (273.15-(32.*5./9.))},
};

/* Quote+Uppercase Dates */
static UNIT *date_unit ;
static UNIT unitSI;		/* Used by unit_si */
static int  ndate_unit ;

#define ITEMS(a)        sizeof(a)/sizeof(a[0])
#define issign(c)      (((c) == '+') || ((c) == '-'))
#define isuni2(c)      (((c)&0xe0)==0xc0)   /* UTF-8 on 2 bytes */


static char edited_unit[MAXUNIT+1];
static int  indunit;
static int   errors;
static char *bufunit = edited_unit;

static double fraction ;
static int comp6[7] ;	/* Components of time 	*/
#define year 	comp6[0]
#define month	comp6[1]
#define day	comp6[2]
#define hour	comp6[3]
#define min	comp6[4]
#define sec	comp6[5]
#define sign	comp6[6]
static double fact6[6] = { 365.25*86400., 30.*86400., 86400.,
	3600., 60., 1. } ;

#if DEBUG
static char inbyte;
#endif

#if UNITp		/* Declare static function */
static int unitec();
#endif

/*==================================================================
		Very Basic Utilities
 *==================================================================*/

/* Some machines do not know strncasecmp */
#ifndef NOstrncasecmp
#define stundiff        strncasecmp
#else
int stundiff(s1,s2, len)
/*++++++++++++++++++++++++
.PURPOSE Compare (or compute difference of) two strings, case insensitive.
.RETURNS A positive value if s1 > s2, zero if strings are identical,
	a negative value if s1 < s2.
-----------*/
  char *s1;	/* IN: address of first string 	*/
  char *s2;	/* IN: address of 2nd string  	*/
  int  len; 	/* IN: Max length to compare	*/
{
  char 	*p, *q, *e;
  char 	cp, cq;
    for (p=s1, e=s1+len, q=s2; p<e ; p++, q++) {
  	cp = toupper(*p), cq = toupper(*q);
	if (cp != cq)	break;
    	if (!cp)	return(0) ;
    }
    return(p == e ? 0 : cp - cq);
}
#endif

static int strnline(s, len)
/*++++++++++++++++
.PURPOSE  Compute length of line (remove trailing spaces)
.RETURNS  The length of the trimmed line
-----------------*/
  char *s;	/* IN: String to scan */
  int len;	/* IN: Valid length of s */
{
  char *p;
    for (p=s+len; p>s;) {
    	--p;
    	if (isgraph(*p)) { p++; break; }
    }
    return(p-s);
}

/*==================================================================*/
static int strloc(text, c)
/*++++++++++++++++
.PURPOSE  Locate specified character
.RETURNS  Index of located char
-----------------*/
  char *text;     /* IN: String to interpret */
  int  c;         /* IN: Char to look for */
{
  char *s;
    for ( s = text; *s; s++)      if (*s == c)    break;
    return(s-text);
}

/*==================================================================
		Adding date_units
 *==================================================================*/

static UNIT *add_quoted(symbol, len)
/*++++++++++++++++
.PURPOSE  Add the quoted symbol as a Unit
.RETURNS  The full (newly allocated) unit.
.REMARKS  Should be made of YMD and punctuations only
-----------------*/
  char *symbol ;	/* IN: The symbol to add */
  int len ;		/* IN: Length of symbol  */
{
  UNIT *u ; int i ;
  char *s, *e, ymd[8] ;	/* ymd made of letter followed by number of symbols */
  char buffer[256] ;

    /* Verify it's a date... */
    s = symbol ;
    if (*s != '"') return((UNIT *)0) ;
    ++s ;
    ymd[0] = 0 ;
    while(*s) {
      if (ispunct(*s)) {
	  if (*s == '"') break ;
	  s++ ;
	  continue ;
      }
      if ((*s == 'Y') || (*s == 'y') || (*s == 'D') || (*s == 'M')) {
	  i = strloc(ymd, *s) ;
	  if (ymd[i]) /* Already here ! */ return((UNIT *)0) ;
	  e = ymd + strlen(ymd) ;
	  *e = *s ; e[1] = 0 ;
	  while (*e == *s) e[1] += 1, s++ ;
	  e += 2 ; *e = 0 ;
      }
      else return((UNIT *)0) ;		/* Invalid date format... */
    }
    if (*(s++) != '"') return((UNIT *)0) ;
    if ((symbol+len) != s) return((UNIT *)0) ;

    if (date_unit)
	 date_unit = (UNIT *)realloc(date_unit, (ndate_unit+1)*sizeof(UNIT)) ;
    else date_unit = malloc(sizeof(UNIT)) ;
    u = date_unit + (ndate_unit)++ ;
    memset(u, 0, sizeof(UNIT)) ;
    u->symbol = strdup(symbol) ;
    u->factor = 86400 ;
    memset(u->mksa, _, sizeof(u->mksa)) ;
    u->mksa[2] += 1 ;				/* In terms of seconds */

    /* Edit the explanation */
    strcpy(buffer, "Date expressed as") ;
    e = " " ;	/* Continuation */
    for (s = ymd; *s; s += 2, e = ", ") switch(*s) {
      case 'Y':
	strcat(buffer, e) ;
	if (s[1] == 2) strcat(buffer, "1900-offsetted ") ;
	strcat(buffer, "Year") ;
	continue ;
      case 'D':
	strcat(buffer, e) ;
	if (s[1] == 1) {			/* Month specified ? 	*/
	    if (ymd[strloc(ymd, 'M')]) s[1] = 2 ;
	    else s[1] = 3 ;
	}
	strcat(buffer, s[1] == 3 ? "Day in year" : "Day in month" ) ;
	continue ;
      case 'M':
	strcat(buffer, e) ;
	if (s[1] == 1) strcat(buffer, "Month name or number") ;
	else if (s[1] == 2) strcat(buffer, "Month number") ;
	else  strcat(buffer, "Month abbreviation") ;
    }
    u->explain = strdup(buffer) ;

    return(u) ;
}

/*==================================================================
		Interpretation of special units (starting by ")
 *==================================================================*/

static char *datime_pic(str)
/*++++++++++++++++
.PURPOSE  Guess the 'format' from a date+time
.RETURNS  The "unit"
-----------------*/
  char *str ;	/* IN: String to scan */
{
  static char sym[] = "YMDhms" ;
  static char pic[32];
  char *s, *p, *e;
  int i, k, dec;
    k = 0; s = str; dec = 0;	/* V3.91: dec set to 1 if decimal point */
    p = pic; e = pic+sizeof(pic)-5;
    while (*s && (k<6)) {
	if (isspace(*s)) { 
	    s++; 
	    if (k==3) *(p++) = ' ';  /* Added V4.08 */
	    continue; 
	}
	if (isdigit(*s)) {
	    for (i=0; isdigit(*s); s++) { if (p<e) *(p++) = sym[k]; i++; }
	    if ((i>2) && (k == 2) && (dec != 1)) {	/* Mod. V4.03 */
		/* More than 2 digits for the day: exchange YY -- DD */
	      char *q;
		for (q=pic; q<p; q++) {
		    if (*q == sym[0]) *q = sym[2];
		    else if (*q == sym[2]) *q = sym[0];
		}
	    }
	    if ((i<2) && (dec != 1)) 	/* V3.91: 2 symbols when no fraction */
		*(p++) = sym[k];
	    if (*s == '.')  {		/* V3.91: Can be a decimal point! */
		if (dec == 0) for (i=0; s[i]; i++) {
		    if (s[i] == '.' ) dec++;
		}
		if (dec == 1) ;	/* Only one dot, it's a decimal point */
		else k++;
	    }
	    else k++;
	    continue;
	}
	if (ispunct(*s) || (*s == 'T')) { *(p++) = *(s++); continue; }
	if (isalpha(*s)) {
	    k = 1;	/* Assume a month ? */
	    for (i=0; isalpha(*s); s++) { if (p<e) *(p++) = sym[k]; i++; }
	    k++;	/* Next is day */
	    continue;
	}
	if (p<e) *(p++) = *s;
	s++;
    }
    *p = 0;
    return(pic);
}

static int get_month(str)
/*++++++++++++++++
.PURPOSE  Interpret the Month Name
.RETURNS  Month number / Error
-----------------*/
  char *str ;	/* IN: String to scan */
{
  static char month_list[] = "JanFebMarAprMayJunJulAugSepOctNovDec" ;
  int j ;
    for (j=0; month_list[j] && stundiff(month_list+j, str, 3); j += 3) ;
    j = 1 + (j/3) ;
    if (j > 12) j = 0 ;
    return(j) ;
}

static int get6(pic, str)
/*++++++++++++++++
.PURPOSE  Get the components of date / sexagesimal
.RETURNS  Number of bytes interpreted in str
-----------------*/
  char *pic ;	/* IN: Units starting by '"' */
  char *str ;	/* IN: String to scan */
{
  char *p, *s ;
  int i, j, c, check_century;
  double factor ;

    p = pic ; s = str ; i = check_century = 0 ;
    fraction = 0 ;
    comp6[0] = comp6[1] = comp6[2] = comp6[3] = comp6[4] = comp6[5] =
    comp6[6] = 0 ;

    if (*p == '"') p++ ;
    if (p[2] != s[2]) while (*s == ' ') s++ ;	/* Could be " D/ M/YYYY" */
    if ((s[0] == '-') && (s[1] != '-')) s++, sign = 1 ;
    else if (*s == '+') s++ ;

    i = 0 ;
    while ( *s && *p && (*p != '"') ) {
	switch(c = *p) {
          case 'y': case 'Y':
	    for (i=0; p[i] == c; i++) ;
	    check_century = i == 2;
	    i=0 ; break ;
	  case 'M': if (i >= 3) i = 4 ; else i = 1 ;
		break ;
	  case 'D': i=2 ; break ;
	  case 'd': /* Degrees */
	  case 'H':
	  case 'h': i=3 ; break ;
	  case 'm': i=4 ; break ;
	  case 'S':
	  case 's': i=5 ; break ;
	  case ':': if (*p != *s) { p++ ; continue ; }
	  default:
	    if (*p == *s) { p++, s++ ; continue ; }
	    if (ispunct(*p) && isspace(*s)) { p++, s++ ; continue ; }
	    errors++ ;
	    fprintf(stderr, "#***date/time/sexa: invalid '%c' component in %s\n",
	      c, pic) ;
	    return(p-pic) ;
	}
	j = 0 ;
	if (p[1] == p[0]) {	/* e.g. YYYYMMDD */
	    if ((i == 1) && isalpha(*s)) {	/* Litteral Month */
		j = get_month(s) ;
		if (!j) errors++, fprintf(stderr,
		    "#***date/time/sexa: invalid alphabetic '%s' for %s\n", str, pic) ;
		while (*p == c) p++ ;
		while (isalpha(*s)) s++ ;
	    }
	    else {
		if ((*p == c) && (*s == ' ')) p++, s++ ;
		while ((*p == c) && isdigit(*s))
		    j = j*10 + (*(s++) - '0'), p++ ;
	    	if (*p == c)  {
		    errors++, fprintf(stderr,
		    "#***date/time/sexa: number '%s' misses digit %s\n", str, pic) ;
		    while (*p == c) p++, s++ ;
		}
	    }
	}
	else {
	    while (*s == ' ') s++ ;
	    if (*s == ':') s++ ;
	    if ((i == 1) && isalpha(*s)) {	/* Litteral Month */
		j = get_month(s) ;
		if (!j) errors++, s++, fprintf(stderr,
		    "#***date/time/sexa: invalid alphabetic '%s' for %s\n", str, pic) ;
		while (*p == c) p++ ;
		while (isalpha(*s)) s++ ;
	    }
	    else {
	        while (isdigit(*s)) j = j*10 + (*(s++) - '0') ;
	        p++ ;
	        if (ispunct(*p) && ((*s == *p) || (*s != '.'))) {
		    if (ispunct(*s)) s++ ;
		    p++ ;
	        }
	    }
	}
	comp6[i] = j ;
	if (*p == 'f') break ;	/* Fraction */
	if (*s == '.') {
	    if (*p == '.') {	/* Could be fraction of day  (V3.91) */
		if ((p>pic) && (p[-1] == p[1])) break;
		p++, s++ ;
	    }
	    else break ;
	}
    }
    if (*p == '"') p++ ;

    if (*s == '.') {
	fraction = atof(s) * fact6[i] ;
	for (++s; isdigit(*s); s++) ;
    }
    else if (*p == 'f') {	/* A fraction */
	for (factor = fact6[i] ; isdigit(*s); s++) {
	    factor /= 10. ;
	    fraction += factor * (*s - '0') ;
	}
	while (*p == 'f') p++;
    }

    /* Verify the 2-digit year: between 01-01-1938 and 31-12-2037 */
    if (check_century) {
	if (comp6[0]<100)  comp6[0] += 1900;
	if (comp6[0]<1938) comp6[0] += 100;
    }

    while (*s == ' ') s++ ;
    return(s-str) ;
}

static double jdate()
/*++++++++++++++++
.PURPOSE  Convert Y M D h m s (stored in comp6) into Julian Date
.RETURNS  The value, in JD
-----------------*/
{
  double JD ;
  int j, jm ;

    if (sign) year = -year ; sign = 0 ;
    if (year <= -4712) {
	j = 1 + (year + 4712)/400;
	jm = -j * 146097L;
	j = year + 400*j;
    }
    else jm = 0 , j = year ;

    /* Convert month to 0-11, day to 1-31.
       If no month specified, assume YYYY.DDD
    */
    if (!comp6[2]) comp6[2] = 1 ;
    if (comp6[1]) {		/* Month Day */
	comp6[1] -= 1 ;
    	/* Convert year mon day into JD */
    	j  -= (11 - month)/10 ;
    	jm += (1461L* ( j + 4712L))/4 + (306L * ((month+10)%12) + 5)/10
	    - (3* ((j + 4900L)/100))/4 + day + 96 ;
    }
    else {			/* Month not specified -- used yday
				   and 400-yr = 146097 days cycle */
	j = year % 400 ; year /= 400 ;
	if (j < 0) { j += 400 ; year-- ; }
	jm = 365*j + (j+3)/4 - (j-1)/100 + (comp6[2]-1) ;
	jm += 1721059L + (year * 146097L) ;
    }
    JD = jm ;
    jm  = ((int4)(12+hour))*3600L + min*60 + sec;
    JD += (jm/86400.e0);
    return(JD) ;
}

static double get_special(unit, str)
/*++++++++++++++++
.PURPOSE  Interpret a "special" string, like date / angle
.RETURNS  The value. The edited_unit is set.
-----------------*/
  char *unit ;	/* IN: Units starting by '"' */
  char *str ;	/* IN: String to scan */
{
  double value ;
  char *u ; int i ;

    value = 0 ;
    u = unit ;
    if (*u == '"') u++ ;

    /* Accept "now" */
    if (strcmp(str, "now") == 0) { time_t now;
	now = time(0);
	value = now; value /= 86400. ;
	value += MJD1970;
	strcpy(edited_unit, "MJD");
	return(value);
    }

    edited_unit[0] = edited_unit[1] = 0 ;
    switch(*u) {
      case 'H': case 'h': case 'm':
	edited_unit[0] = 's' ; 	/* Seconds */
	edited_unit[1] =  0  ; 	/* Seconds */
	i = get6(unit, str) ;
	value = comp6[3]*3600. + comp6[4]*60. + comp6[5] + fraction ;
	break ;
      case 'y': case 'Y': case 'M': case 'D':
	strcpy(edited_unit, "JD");
	i = get6(unit, str) ;
	value = jdate() + fraction/86400.  ;
	break ;
      case 'd':			/* degree */
	strcpy(edited_unit, "deg") ;
	i = get6(unit, str) ;
	value = comp6[3] + comp6[4]/60. + (comp6[5]+fraction)/3600. ;
	break ;
      default:
	errors++ ;
	strcpy(edited_unit, unit) ;
	return(0./0.) ;
    }
    if (i == 0) {		/* No digit ==> NaN */
	value = 0./0.;
    }
    else if (sign) 		/* Take care of sign ... */
        value = -value ;

    /* Verify string exhausted */
    if (str[i]) errors++, fprintf(stderr,
	"#***date/time/sexa: bad char '%c' in for '%s'\n", str[i], str) ;
    return(value) ;
}

/*==================================================================
		Operations on UNIT
 *==================================================================*/

static void powu(unit, power)
/*++++++++++++++++
.PURPOSE  Compute power of unit
.RETURNS  ---
-----------------*/
  UNIT *unit;	/* MOD: Unit */
  int  power;	/* IN: Exponent */
{
  double r; int i, j;
    if (unit->log_factor) fprintf(stderr, 
	    "#***Can't combine log[units] power %d\n", power) ;
    r = 1.0; i = power;
    while (i > 0) r *= unit->factor, i--;
    while (i < 0) r /= unit->factor, i++;
    unit->factor = r;
    for (j=0; j<8; j++)
	unit->mksa[j] = _ + ((unit->mksa[j] - _) * power);
}

static void uxu(u1, u2)
/*++++++++++++++++
.PURPOSE  Compute product of units
.RETURNS  ---
-----------------*/
  UNIT *u1;	/* MOD: Unit */
  UNIT *u2;	/* IN: operand */
{
  int j;
    if (u1->log_factor || u2->log_factor)
	fprintf(stderr, "#***Can't multiply log[units]\n") ;
    u1->factor *= u2->factor;
    for (j=0; j<8; j++)
	u1->mksa[j] += (u2->mksa[j] - _);
}

static void udu(u1, u2)
/*++++++++++++++++
.PURPOSE  Compute division of units
.RETURNS  ---
-----------------*/
  UNIT *u1;	/* MOD: Unit */
  UNIT *u2;	/* IN: dividend */
{
  int j;
    if (u1->log_factor || u2->log_factor)
	fprintf(stderr, "#***Can't divide log[units]\n") ;
    if (u2->factor) u1->factor /= u2->factor;
    else u1->factor = 0;
    for (j=0; j<8; j++)
	u1->mksa[j] -= (u2->mksa[j] - _);
}

/*==================================================================
		Internal utilities
 *==================================================================*/

static char *edf(factor)
/*++++++++++++++++
.PURPOSE  Edit a Factor (only if differs from 1)
.RETURNS  Buffer with edited Factor
-----------------*/
  double factor;/* IN: Factor to edit */
{
  static char buf[25];
  int i; char *p;
  double val, fabs();

    p = buf;
				/* Edit factor if differs from 1 */
    if (factor < 0)  *(p++) = '-';
    val = fabs(factor);
    if (val == 0) *(p++) = '?';
    else if ((fabs(val-1) > 1.e-9) && (fabs(val-1) <= 1.e99)) {
	for (i=0; val > 9.99999999; val /= 10.0) i++;
	while (val < .9999999999) val *= 10.0, i--;
	if (fabs(val-1) > 1.e-9) {
	    sprintf(p, "%g", val), p += strlen(p);
	    if (i) *(p++) = 'x';
	}
	if (i) sprintf(p, "10%+d", i), p += strlen(p);
    }
    *p = 0;

    return(buf);
}

static char *edu(pmksa)
/*++++++++++++++++
.PURPOSE  Edit a unit (without factor)
.RETURNS  Buffer with edited unit
-----------------*/
  char pmksa[8];/* IN: Powers to edit */
{
  static char buf[8*8+MAXEXU+1];
  int i, j, nu; char *p, sep;

    p = buf; *p = 0;

		/* Check unitless	*/
    for (nu=0, j=0; j<8; j++) {
    	if (pmksa[j] == _ ) continue;
    	nu += pmksa[j] == (_+1) ? 1 : 2;
    }
    if (nu == 0) return(buf);

		/* Find out the unit	*/
    for (i=0; i<ITEMS(unit); i++) {
    	if (unit[i].factor != 1.0) continue;
    	if (strncmp(unit[i].mksa, pmksa, 8)) continue;
    	if (unit[i].offset != 0.0) continue;
    	strcpy (p, unit[i].symbol);
    	p += strlen(p);
    	break;
    }
    if (nu == 1) return(buf);	/* Single unit, no explanation required	*/

    if (p != buf) *(p++) = ' ', *(p++) = '(';
    else nu = 0;

		/* Add the MKSA symbols */
    for (j=0, sep=0; j<8; j++) {
	i = pmksa[j] - _;
	if (!i) continue;
	if (sep) *(p++) = '.'; sep = 1;
	strcpy(p, mksa[j]); p += strlen(p);
	if (i != 1) sprintf(p, "%+d", i), p += strlen(p);
    }
    if (nu) *(p++) = ')';
    *p = 0;

    return(buf);
}

static int uwrite(text, len)
/*+++++++++++++++
.PURPOSE  Append text to bufunit
.RETURNS  Number of bytes inserted
-----------------*/
  char *text;	/* IN: Text to append */
  int len;	/* IN: Length (stopped at NUL) */
{
  char *p, *e;
#if DEBUG>1
    { int i;
    printf ("....uwrite \"");
    for (p=text, e=p+len, i=indunit; *p && (p<e) && i<=MAXUNIT; p++,i++)
    	putchar(*p);
    printf("\", indunit=%d\n", indunit) ;
    }
#endif

    if (len < 0) len = strlen(text) ;
    for (p=text, e=p+len; *p && (p<e) && indunit<=MAXUNIT; p++,indunit++)
    	bufunit[indunit] = *p;
    bufunit[indunit] = 0;
    return(p-text);
}

static int uwritex(char *text)
/*+++++++++++++++
.PURPOSE  Append explanation
.RETURNS  Number of bytes inserted
.REMARKS  Ignore the leading question mark
-----------------*/
{
  char *p; int n=0, q;
    p = text;
    if ((q = *p == '?')) p++;
    n = uwrite(p, -1);
    if (q) n += uwrite(" (*)");
    return(n);
}

static int ubad(text, len)
/*++++++++++++++++
.PURPOSE  Insert the "bad unit"
.RETURNS  Number of bytes inserted
.REMARKS  The text is preceded by a question mark
-----------------*/
  char *text;	/* IN: Text to append */
  int len;	/* IN: Length (stopped at NUL) */
{
  int i;
    if (indunit > MAXUNIT-3) indunit = MAXUNIT-3;
    i = indunit;
    bufunit[indunit++] = '<';
    bufunit[indunit++] = '?'; 
    uwrite(text, len); uwrite(">", 1);
    errors++;
    return(indunit - i);
}

static int ubad3(text, len, buf)
/*++++++++++++++++
.PURPOSE  Insert the "bad unit"
.RETURNS  Number of bytes inserted
.REMARKS  The text is preceded by a question mark
-----------------*/
  char *text;	/* IN: Text to append */
  int len;	/* IN: Length (stopped at NUL) */
  char *buf;	/* OUT: Buffer where text appended */
{
  int oi, i; char *ob;
    oi = indunit; ob = bufunit;		/* Save */
    indunit = 0; bufunit = buf;
    i = ubad(text, len);
    indunit = oi; bufunit = ob;		/* Restore */
    return(i);
}

static UNIT *ufind(table, items, symbol, len)
/*++++++++++++++++
.PURPOSE  Lookup in the known symbols
.RETURNS  The explanation
-----------------*/
  UNIT *table;	/* IN: Table to look at */
  int  items;	/* IN: Items in table	*/
  char *symbol;	/* IN: Symbol to interpret */
  int  len;	/* IN: Length of symbol -- 0 means longest */
{
  UNIT *pu; int i, j;

    for (pu=table, i=items; --i>=0; pu++) {
	j = len == 0 ? strlen(pu->symbol) : len;
	if (strncmp(pu->symbol, symbol, j) == 0){ /* label match */
	    if (pu->symbol[j]) continue;
	    return(pu);
	}
    }
    return ((UNIT *)0);
}

/*==================================================================
		Interpretation routine
 *==================================================================*/

int unit_factor(text, value)
/*++++++++++++++++
.PURPOSE  Interpret a number as (+/-)numx10+pow
.RETURNS  Number of bytes interpreted
-----------------*/
  char *text;		/* IN: The unit to explain */
  double *value;	/* OUT: Result		*/
{
  char *p, bra;
  double atof(), b;
  int i, has_e;
    *value = 1.0;
    p = text;
    b = 0; bra = 0;
    if (issign(*p)) p++;
    if (isdigit(*p) || *p == '.') {
    	if (issign(*p)) p++;
    	while (isdigit(*p)) p++;
    	if (*p == '.') p++;
    	while (isdigit(*p)) p++;
	has_e = ((*p == 'e') || (*p == 'E')) && (issign(p[1]) || isdigit(p[1]));
	*value = atof(text);
	if (has_e) {
	    ++p; if (issign(*p)) p++;
	    while (isdigit(*p)) p++;
	}
    	else if (*p == 'x' && isdigit(p[1])) {	/* Factor */
	    p++;
	    b = atof(p); i = 0;
    	    while (isdigit(*p)) p++ ;
	}
	if (has_e) ;
    	else if (issign(*p) || (*p == '^') || ((p[0] == '*')&&(p[1] == '*'))) {
	    if (!b) b = *value, *value = 1;
	    if (*p == '^') p++ ;
	    else if (*p == '*') p += 2;
	    while (*p == '(') { p++; bra++; }	/* V4.01: accept () */
	    i = atoi(p), p++;
    	    while (isdigit(*p)) p++;
	    while ((--bra>=0) && (*p == ')')) p++;         /* V4.01 */
	    if (*p == '^') p++ ;		  /* e.g. 10^12^ */
	    while (i > 0) *value *= b, i--;
	    while (i < 0) *value /= b, i++;
    	}
	if ((*p == '.') || (*p == '*')) p++ ;	  /* e.g. 10^6.g */
    }
    else p = text;	/* We could have "---" */

    return(p-text);
}

static int unit1(text, result)
/*++++++++++++++++
.PURPOSE  Explain a single_Unit
.RETURNS  The length of interpreted string
.REMARKS  Recursive
-----------------*/
  char *text;	/* IN: The unit to explain */
  UNIT *result;	/* OUT: factor & mksa are set */
{
  UNIT *u, *m, *pu, sunit;
  char *p, op[12];
  int  i, s, power, bra;
  char *obuf, nbuf[MAXUNIT+1]; int oind;

#if DEBUG
	printf("....unit1:   examine <%s>", text);
#endif

    op[0] = op[1] = 0; power=0; /* Power operator	   */
    bra = 0;
    u = m = (UNIT *)0;		/* Found Unit / Multiplic. */
    sunit.explain = nbuf; nbuf[0] = 0;
    sunit.factor  = sunit.log_factor = sunit.offset = 0;
    for (i=0; i<8; i++) sunit.mksa[i] = '0';

    *result = unit[0];		/* Initialize to unitless */

    switch(*(p = text)) {
#if UNITp		/*=======Allow parentheses========*/
    case '(':		/* Match parenthetized expression */
    	obuf = bufunit; oind = indunit;	/* Save old buffer*/
    	bufunit = nbuf; indunit = 0;  	/* Set new buffer */
	u = &sunit;
    	uwrite(p++, 1);
    	p += unitec(p, u);		/* Recursive call */
    	    				/* Remove trailing spaces */
    	indunit = strnline(bufunit, indunit);
    	if (*p == ')') uwrite(p++, 1);
    	else uwrite("?missing)", 9);
    	bufunit = obuf; indunit = oind; 	/* Reset old buf. */
    	break;
#endif
    case '"':  	/* Units within quotes must match exactly */
    	p++; s = strloc(p, '"'); if (p[s]) s++;
    	u = ufind(unit, ITEMS(unit), p-1, s+1);
    	if ((!u) && date_unit) u = ufind(date_unit, ndate_unit, p-1, s+1);
	if (!u) u = add_quoted(p-1, s+1) ;
	if (!u) ubad3(p-1, s+1, nbuf);
    	p += s;
    	break;
    case '#':					/* Offseted unit */
    	obuf = bufunit; oind = indunit;	/* Save old buffer*/
    	bufunit = nbuf; indunit = 0;  	/* Set new buffer */
	p++; uwrite("(", 1);
	for (i=0; p[i] && (p[i]!='#') && (p[i]!='+') && (p[i]!='-'); i++) ;
	s = p[i]; p[i] = 0;
	unitec(p, &sunit);
	uwrite("shifted ", -1);
	p[i] = s; p += i;
	i = strloc(p, '#');
	uwrite(p, i);
	sunit.offset = 0 - atof(p);
	p += i; if (*p) p++;
	uwrite(")", 1);
	/* u = unit;				-- unitless */
    	bufunit = obuf; indunit = oind; 	/* Reset old buf. */
	break;
    case '-':		/* Unitless ?? */
    	if (!isdigit(p[1])) {
    	    while (*p == '-') p++;
	    u = ufind(unit, ITEMS(unit), "---", 3);
    	}
    	break;
    default:		/* Look for Unit */
	s = 0;
	if (*p == '%') s = 1;		/* The '%' is accepted	*/
	else {
	    if (*p == '\\') s = 1;
    	    while (isalpha(p[s]) || isuni2(p[s])) { /* Mod. V4.11 */
		if (isuni2(p[s])) s += 2;
		else s++; 
	    }
	    /* Added V4.23: accept a0, .. */
	    if (p[s] == '0') s++;
	}
    	if (s == 0) {
	    /* Accept Angstroem if (*p == 'Å') s++; */  /* Mod. V4.11 */
	    return(0);   				/* Unitless	*/
	}
    	if ((u = ufind(unit, ITEMS(unit), p, s))) {	/* Simple unit 	*/
	    p += s;
    	    break;
    	}
	if ((*p=='\\') && (s>1) && (u=ufind(unit, ITEMS(unit), p+1, s-1))) {
	    p += s;
	    break;
	}
    	    		/* Simple unit not found. Look for multiple prefix */
	if (s > 2) {
	    m = ufind(multiple, ITEMS(multiple), p, 2);
	    if (m) p += 2, s -= 2; 		/* 2-letter prefix */
	}
        if ((!m) && (s > 1)) {
            m = ufind(multiple, ITEMS(multiple), p, 1);
    	    if (m) p += 1, s -= 1;
	}
    	u = ufind(unit, ITEMS(unit), p, s); 	/* Simple unit */
    	if (!u)  ubad3(p, s, nbuf);
    	p += s;
    	break;
    }
						/* Look for power */
    if (*p == '^') p++;
    else if ((p[0] == '*') && (p[1] == '*')) p += 2;
    while (*p == '(') { bra++; p++; }
    power = atoi(p); op[0] = '+' ;
    if (issign(*p))  op[0] = *(p++);
    if ((power <= -16) || (power >= 16)) {	/* IMPOSSIBLE... */
	return(0);
    }
    if ((power > 0) && (power < 10)) {		/* Square, etc 	*/
	pu = ufind(operator, ITEMS(operator), p, 1);
	if (pu) uwritex(pu->explain), op[0] = 0;
    }
    while (isdigit(*p)) p++;
    while ((--bra>=0) && (*p == ')')) p++;
    if (*p == '^') p++;				/* accept e.g. m^3^ */

    if (!u) u = &sunit;
    *result = *u;
    if (m) { 					/* Multiplicity prefix */
	result->factor *= m->factor;
	uwritex(m->explain);
    }
    uwritex(u->explain);
    if (power) {
	if (op[0]) {
	    pu = ufind(operator, ITEMS(operator), op, 1);
	    uwritex(pu->explain);
	    sprintf(op, "%+d", power);
	    uwrite(op+1, -1);
	}
	powu(result, power);
    }

#if DEBUG
	printf(" =>%d\n", p-text);
#endif
    return(p-text);
}

static int unitec(text, result)
/*++++++++++++++++
.PURPOSE  Match the compound unit = factor unit [operator unit]...
.RETURNS  The length of interpreted string
.REMARKS  Recursive
-----------------*/
  char *text;	/* IN: The unit to explain */
  UNIT *result;	/* OUT: Resulting factor + units */
{
  UNIT *u, sunit;
  double log_factor ;
  char *p, end;
  void (*opu)();
  int i, op;
    		/* Get the initial factor */
#if DEBUG
	printf("....unitec: examine <%s>\n", text);
#endif
    result->symbol = text; result->explain = bufunit;
    result->factor = 1.0; for (i=0; i<8; i++) result->mksa[i] = _;
    result->log_factor = log_factor = result->offset = 0 ;
    opu = uxu;		/* Operator is multiplication */
    p = text;
    p += unit_factor(p, &(result->factor));
    if (p != text) uwrite(text, p-text), uwrite(" ", 1);
    if (*p == '[') {	/* LOG of unit ... */
	log_factor = result->factor ;
	result->factor = 1.0;
	uwrite("log[ ", -1) ;
	end = ']'; p++ ;
	i = unit_factor(p, &(result->factor));
        if (i) uwrite(p, i), uwrite(" ", 1), p += i;
    }
    else end = 0 ;

    		/* Scan symbols / operators */
    for(op=0; *p; op++) {
	if (*p == end) { 			/* Closing log	*/
	    uwrite(&end, 1) ; end = 0 ;
	    result->log_factor = log_factor ; log_factor = 0 ;
	    p++ ;
	    continue ;
	}
    	i = unit1(p, &sunit);			/* Match a Unit	*/
    	if (i == 0) {				/* Try factor	*/
    	    i = unit_factor(p, &(sunit.factor));
    	    if (i) uwrite(p, i), uwrite(" ", 1);
    	}
	(*opu)(result, &sunit);
	if (op == 0) result->offset = sunit.offset;
    	p += i;
#if UNITp		/*=======Allow parentheses========*/
    	if (*p == ')') break ;
#endif
	if (*p == end) continue ;
    	u = ufind(operator, ITEMS(operator), p, 1);
	/* Accept the blank as a multiplicator */
	if (!u) {
	    if (*p == ' ') u = ufind(operator, ITEMS(operator), "*", 1) ;
	}
    	if (!u) break;			/* Must have an operator*/
    	p++;
	if (u->mksa[0] == '/') opu = udu;	/* Division */
	else  opu = uxu;			/* Multiplication */

    	    /* Be sure the explanation is preceded by a space:
    	       remove first trailing spaces, then insert a blank.
	    */
    	indunit = strnline(bufunit, indunit);
    	uwrite(" ", 1);
    	uwritex(u->explain);
    }
    if (end) result->log_factor = log_factor,
	uwrite("?missing]", -1) ;
    return(p-text);
}

/*==================================================================
		External routine
 *==================================================================*/

char *unit_dim(text)
/*++++++++++++++++
.PURPOSE  Find the Unit Dimension
.RETURNS  A string "M+xL+yT+zA+aK+b"  (Mass / Length / Time / Ampere / Kelvin)
.REMARKS  The returned string includes '?' for problems
-----------------*/
  char *text;		/* IN: The unit to explain */
{
  char dim[10];
  UNIT result;
  int i;
    indunit = 0;	/* Initialize buffer */
    errors  = 0;
    i = unitec(text, &result);
    indunit = 0;	/* Initialize buffer */
    edited_unit[0] = 0;
    if (text[i]) ubad(text+i, strlen(text+i));
    else if (errors) ubad(text, strlen(text));
    else for (i=0; sydim[i]; i++) {
	if (result.mksa[i] == _) continue;
	dim[0] = sydim[i];
	sprintf(dim+1, "%+d", result.mksa[i] - _);
	uwrite(dim, -1);
    }
    return(edited_unit);
}

char *unit_si(char *text, double *result)
/*++++++++++++++++
.PURPOSE  Explain a unit
.RETURNS  The SI units (with power) involved
.REMARKS  The returned string includes '?' for problems, 
	  and offset marked by '#', e.g. degC --> K#-273.15#
-----------------*/
{
  char obuf[40], ubuf[40];
  int i; char *p;
  double value;
    indunit = 0;	/* Initialize buffer */
    errors  = 0;
    value = 0;
#if 0
    i = unit_factor(text, &value);
    if (i==0) value = 0./0.;
    text += i;
    i = unitec(text, &unitSI);
    if (text[i]) ubad(text+i, strlen(text+i)), unitSI.factor = 0;
#else
    edited_unit[0] = 0;
    p = unit_get1(text, &value);	/* value with p=unit */
    strncpy(ubuf, p, sizeof(ubuf)); ubuf[sizeof(ubuf)-1] = 0;
    i = unitec(ubuf, &unitSI);
    if (ubuf[i]) ubad(ubuf+i, strlen(ubuf+i)), unitSI.factor = 0;
#endif
    if (errors == 0) indunit = 0;
    bufunit[indunit] = 0;	/* Remove the standard interpretation */

    if (unitSI.log_factor) {
	*result = unitSI.log_factor ;
	if (value == value) *result *= value;	/* Not done if NaN */
        uwrite("[", 1) ;
	uwrite(edf(unitSI.factor), -1) ;
    }
    /* Remove the unit offset -- possible only if value given */
    else if (unitSI.offset && (value == value)) {
	*result = value*unitSI.factor + unitSI.offset;
	unitSI.offset = 0;
    }
    else {
        *result = unitSI.factor;
	if (value == value) *result *= value;	/* Not done if NaN */
    }
    if (unitSI.offset)      uwrite("#", 1);
    p = edu(unitSI.mksa);   uwrite(p, -1);
    if (unitSI.offset) {
	sprintf(obuf, "%f", -unitSI.offset);
	/* Remove useless ending Zero'es */
	for (p=obuf+strlen(obuf)-1; (p>obuf) && *p == '0'; p--) ; *++p = 0;
	if (obuf[0] != '-') uwrite("+", 1);
	uwrite(obuf, -1);
	uwrite("#", 1);
    }
    if (unitSI.log_factor) uwrite("]", 1) ;

    return(edited_unit);
}

char *unit_get1(char *text, double *value)
/*++++++++++++++++
.PURPOSE  Interpret a text, and give the result in value + symbolic unit
.RETURNS  The symbolic unit in which value is expressed; 
	   text in case of error.
.REMARKS  Works also for JD and quoted units.
	  Stops at first blank.
-----------------*/
{
  char buf[32], *p, *e;
  int i;

    errors = 0 ;
    p = text; e = (char *)0;
    *value = 0./0.; 	/* NaN by default */

    /* Simplify: in case of empty unit */
    if (!text[0]) return(text);

    /* Verify first a date  or a "special" unit */
    if (p[0] == '"') e = strchr(p+1, '"');
    if (e) {					/* "Special" unit ------  */
	i = (++e-p);				/* Length of "special"    */
	if (i >= sizeof(buf)-2);		/* Too long "special"	  */
	else if (isgraph(*e)) {			/* e.g. "date"22-Sep-2006 */
	    strncpy(buf, text, i); buf[i] = 0;
	    return(unit_get(buf, e, value));	/* Must parse the date	  */
	}
	/* Here I've only a unit, no following value */
    }

    /* Could be JD or MJD, followed by a number */
    if (strcmp(text, "now") == 0) { 
	 *value = get_special("", "now");
	return(edited_unit);
    }
    if ((toupper(text[0]) == 'J') || (toupper(text[1] == 'J'))) {
        e = "MJD";
	if((toupper(text[0]) == 'M') && (toupper(text[2]) == 'D')) ;
	else if (toupper(text[1]) == 'D') ++e ;		/* MJD --> JD */
	else e = (char *)0;
	if (e) {
	    i = unit_factor(text+strlen(e), value);
	    if (i == 0) *value = 0./0.;
	    return(e);
	}
    }
    /* "Standard" unit: get value + unit */
    i = unit_factor(text, value);
    if (i == 0) 	/* No value ... */
	*value = 0./0.;
    return(text+i);
}

char *unit_get(unit, text, value)
/*++++++++++++++++
.PURPOSE  Interpret a text from unit, and return value
.RETURNS  The SI units (with power) of value
.REMARKS  The returned string includes '?' for problems
	unitSI is set.
-----------------*/
  char *unit;		/* IN: The unit  part */
  char *text;		/* IN: The value part */
  double *value;	/* OUT: the value in the returned units */
{
  char buf[12], *p, *e;
  double atof();
  int i;
    errors = 0 ;
    if (*unit == '"') 	{	/* Special Interpretations */
	if ((strncmp(unit+1, "Y-M-D",5) == 0)
	  ||(strncmp(unit+1, "dat", 3) == 0)) {	/* Full Date Interpretation */
	    p = text && *text ? datime_pic(text) : "Y-M-D";
	    *value = get_special(p, text);
	}
	else if (strncmp(unit+1, "mon", 3) == 0) {
	    for (i=0; (i<sizeof(buf)-1) && text[i]; i++) buf[i] = 'M' ;
	    buf[i] = 0 ;
	    *value = get_special(buf, text) ;
	}
	else *value = get_special(unit, text) ;
    }
    else {
	unit_si(unit, value) ;
	if (text && *text) *value *= atof(text) ;
	if (unitSI.offset) {
	    *value += unitSI.offset;
	    if ((p = strchr(edited_unit, '#'))) {   /* Remve the #+offset# */
	        for (i=1; p[i] && (p[i] != '#'); i++) ;
		if (p[i]) i++;
		strcpy(p, p+i);
	    }
	}
    }
    /* unitec(edited_unit, &unitSI); */
    if (errors && (edited_unit[0] != '?')) {
	p = e = edited_unit + strlen(edited_unit) ;
	while (p >= edited_unit) p[1] = p[0], p-- ;
	*e = 0 ;
	edited_unit[0] = '?' ;
    }
    return(edited_unit) ;
}

static char *for_tex(char *text)
/*++++++++++++++++
.PURPOSE  Rewrite a text for latex
.RETURNS  The TeXised equivalent
.REMARKS  Done in static area. op=4 if we must transform exponents
-----------------*/
{
  static char buf[256]; int i, was_digit=0, in_expo=0;
    for (i=0; *text && (i<sizeof(buf)-6); was_digit=isdigit(*text), text++) {
	switch(*text) {
	  case '\\': buf[i++] = *text;  buf[i++] = 'b';  buf[i++] = ' ';
	    continue;
	  case '_': case '{': case '}':
	  case '%': buf[i++] = '\\'; buf[i++] = *text;
	    continue;
	  case 'x': 
	    if (text[1]=='1' && was_digit) { strcpy(buf+i, "\\times"); i += 6; }
	    else buf[i++] = *text;
	    continue;
	  case '.': 
	    if (isalpha(text[1])) { strcpy(buf+i, "\\cdot "); i += 6; }
	    else buf[i++] = *text;
	    continue; 
	  case '+': case '-':
	     if (isdigit(text[1]) && i) {  buf[i++] = '^'; in_expo=1; }
	     buf[i++] = *text;
	     continue;
	  default:
	    if (in_expo && (!isdigit(*text)))  { buf[i++] = '^'; in_expo=0; }
	    buf[i++] = *text; continue;
	}
    }
    if(in_expo) buf[i++] = '^';
    buf[i] = 0;
    return(buf);
}

char *unitex(text, option)
/*++++++++++++++++
.PURPOSE  Explain a unit
.RETURNS  The explained unit
.REMARKS  The returned string includes '?' for problems
-----------------*/
  char *text;	/* IN: The unit to explain */
  int option;	/* IN: Option as 0=Explain, 1=SI value, 2=both 
                       4=soft-tex edition, e.g. m^2^ instead of m+2 */
{
  UNIT result;
  int i;
    indunit = 0;	/* Initialize buffer */
    errors  = 0;
#if 0
    if (option&4) {	/* V4.05: softex edition */
	/* i used as indicator 'in exponent' */
	for (i=0; *text && (indunit<MAXUNIT); text++) {
	    if (i && (!isdigit(*text))) {
		edited_unit[indunit++] = '^';
		i = 0;
	    }
	    else if (*text == '+') {
		edited_unit[indunit++] = '^';
		i = 1;
		continue;
	    }
	    else if (*text == '-') {
		edited_unit[indunit++] = '^';
		i = 1;
	    }
	    edited_unit[indunit++] = *text;
	}
	if (i) edited_unit[indunit++] = '^';
	edited_unit[indunit] = 0;
	return(edited_unit);
    }
#endif

    i = unitec(text, &result);
    if (text[i]) ubad(text+i, strlen(text+i)), result.factor = 0;
    indunit = strnline(bufunit, indunit);	/* Remove trailing blanks */
    bufunit[indunit] = 0;

    if(option && edited_unit[0]) {
	if ((option&1) == 1)  		/* Remove the explanations */
	    indunit = 0;
	else uwrite(" = ", 3);
	if (result.log_factor) {
	    uwrite(edf(result.log_factor), -1) ;
	    uwrite("[", -1) ;
	}
	uwrite(edf(result.factor), -1) ;
	uwrite(edu(result.mksa), -1) ;
	if (result.log_factor) uwrite("]", 1) ;
	if(option&4) return(for_tex(edited_unit));
    }
    return(edited_unit);
}

double unit_scale(char *u1, char *u2)
/*++++++++++++++++
.PURPOSE  Compute the scaling factor from unit u1 to unit u2
.RETURNS  The scaling factor f such that  x2 = f * x1 / 0 when error
.REMARKS  The conversion angle -> time is taken into account
-----------------*/
{
  char si1[40], *p1, *p2; double f, f1, f2 ;
    f1 = f2 = 1.0 ;
    p1 = p2 = "" ;
    if (*u1) {
	p1 = unit_si(u1, &f1) ;
	strncpy(si1, p1, sizeof(si1));
	p1 = si1 ;
    }
    if (*u2) p2 = unit_si(u2, &f2) ;
    f = f1 / f2 ;
    if (strcmp(p1, p2) == 0) 	/* Compatible Units */
	return(f) ;

    if ((*p1 == 0) && (strcmp(p2, "s") == 0))
	return(f*86400.) ;
    if ((*p2 == 0) && (strcmp(p1, "s") == 0))
	return(f/86400.) ;

    /* INCOMPATIBLE UNITS */
    return(0.) ;
}

double unit_ghz(char *text) 
/*++++++++++++++++
.PURPOSE  Interpret a text for a frequency / wavelength
.RETURNS  The frequency, in GHz / NaN if error
.REMARKS  Example: "1eV" returns the corresponding frequency
-----------------*/
{ 
  char *u; double value;
    /* Initialisation: c (light speed) + Planck (2.pi\h) */
    if (!text) return(0./0.);
    if (isdigit(*text)); else return(0./0.);
    u = unit_si(text, &value);
    if (!u) return(0./0.);
    /*
    fprintf(stderr, "#...unit_freq(%s) => %s, value=%g\n", text, u, value);
    */
    if (*u == 'm') { 	/* I have a wavelength */
	if (u[1] == 0) 
	    return(1.e-9*_c_/value);
	if (strcmp(u, "m-1")==0) /* Wavenumber */
	    return(1.e-9*_c_*value);
    }
    else if (strncmp(u, "Hz", 2) == 0)  /* I have a frequency */
	return(1.e-9*value);
    else if (*u == 'J') {
	if ((u[1] = ' ') || (u[1] == '0')) 
	    return(1.e-9*value/_h_);
    }
    /* Not wavelength, wavenumber, frequency, energy... */
    return(0./0.);
}

double unit_um(char *text) 
/*++++++++++++++++
.PURPOSE  Interpret a text for a wavelength
.RETURNS  The wavelength, in um (microns)
.REMARKS  Example: "1eV" returns the corresponding 
-----------------*/
{ 
    return(1.e-3*_c_/unit_ghz(text)) ;
}

#ifdef TEST
#include <signal.h>

static char *theQuestion;
static int qno = 0;

static char usage[] = "\
Usage: units [-help] [-version] [-tex] [?] [-um|-GHz] [complex_value|-]...";
static char help[] = "\
   -help: display this help\n\
    -tex: edit unit in `softex'\n\
       ?: display all known units\n\
       -: get values to interpret from stdin\n\
     -um: compute wavelength, in microns\n\
    -GHz: compute freqsuency, in Giga-Hertz\n\
   value: complex values to explain (default: values in standard input)\n\
  accepts value as GHz:text  or um:text for wavelength/frequency\n\
" ;

/*==================================================================
		Sort routine
 *==================================================================*/

static int cmpu(u1, u2)
/*++++++++++++++++
.PURPOSE  Compare 2 units
.RETURNS  the first (alphabetical order)
.REMARKS  Comparison dictionary order
-----------------*/
  UNIT *u1;
  UNIT *u2;
{
  char *s1, *s2; int c1=0, c2=0, d, b;
    for (s1=u1->symbol, s2=u2->symbol, d=0, b=0 ;(d==0)&&(*s1|*s2); s1++, s2++){
	c1 = *s1&0xff ; c2 = *s2&0xff ;
	if (c1 == '-')   { d = c2 == '-' ? 0 : -1 ; continue ; }
	if (c2 == '-')   { d = c1 == '-' ? 0 :  1 ; continue ; }
	if (c1 == '\\') { b = 1; s2--; continue; }
	if (c2 == '\\') { b = -1; s1--; continue; }
	if (c1 == '"')  { d = c2 == '"' ? 0 :  1 ; continue ; }
	if (c2 == '"')  { d = c1 == '"' ? 0 : -1 ; continue ; }
	d = toupper(c1) - toupper(c2) ;
    }
    if (d == 0) d = c1 - c2 ;
    if (d == 0) d = b;
    return(d) ;
}

static void too_long(s)
/*++++++++++++++++
.PURPOSE  Verify the config file changed
.RETURNS  --
-----------------*/
  int s;        /* IN: Signal number */
{
    fprintf(stderr, "#***units too long (loop?) qno=%d: %s\n",
      qno, theQuestion ? theQuestion : "(nil)");
    fflush(stderr);
    sleep(2);
    exit(1);
}

static char *head[] = {
  "#===List of all units and constants==================\n", 
  "\\par{\\bf List of known Units} \\qquad\
  {\\em\\fg{DarkGreen}(the (*) indicates non-standard units)}\n\
\\begin{TABULAR}{lpl}\n" };
static char *tail[] = { "", "\\end{TABULAR}\n" };
static char *colors[] = { "\\bg{cornsilk}", "\\bg{DarkSeaGreen1}" };
static char *qcol = "\\fg{gray30}" ;  /* Color when questionable unit */

/*==================================================================
		Test Program
 *==================================================================*/
int main(int argc, char **argv)
/*++++++++++++++++
.PURPOSE  Just a TEST program
-----------------*/
{
  char buf[512], my_unit[32], *p, *got;
  int i, from_stdin;
  int optex = 0;
  int optw = 0; 	/* 1=GHz, 2=um */
  double f, value;

    for (++argv, --argc; argc > 0; argc--, argv++) {
	p = *argv;
	if (*p != '-') break;
	if (strncmp(p, "-ver", 4) == 0) {
	    printf("units (CDS) Version %s\n", VERSION);
	    exit(0);
	}
	if (p[1] == 'h') {
	    printf("%s\n%s", usage, help);
	    exit(0);
	}
	if (p[1] == 't') {
	    optex = 1;
	    continue;
	}
	if (strcmp(p, "-um") == 0) {
	    optw |= 2;
	    continue;
	}
	if (strcmp(p, "-GHz") == 0) {
	    optw |= 1;
	    continue;
	}
	if (p[1] == 0) continue;
	fprintf(stderr, "#***Unknown argument: %s\n%s\n", p, usage);
	exit(1);
    }
    
    if (argc == 0) /* form_stdin&2: prompt */
	 from_stdin = isatty(0) ? 3 : 1 ;
    else from_stdin = 0;

    while(1) {
        signal(SIGALRM, too_long); alarm(12000);
	if (from_stdin&2) printf("\nGive a unit: ");
	if (from_stdin&1) got = fgets(buf, sizeof(buf), stdin);
	else if (argc>0)  got = *(argv++), --argc;
	else              got = (char *)0;
	if (!got) break;
        theQuestion = got;
	/* Remove heading  blanks: */
	while (*got == ' ') got++;
	/* Remove trailing blanks: */
	for (i=strlen(got)-1; (i>=0) && isspace(got[i]); i--); got[++i] = 0;
	/* Remove blanks after value */
	i = strloc(got, ' ');
	if (got[i]) { char *ps;
	    ps = got+i;
	    for (i=0; ps[i] == ' '; i++) ;
	    while(ps[i]) { ps[0] = ps[i]; ps++; }
	    *ps = 0;
	}
	if ((!*got) || (*got == '?')) {	/* Help */ char *fg; int op=optex<<2;
	    qsort(unit, ITEMS(unit), sizeof(UNIT), (SORT_FCT)cmpu);
	    printf("%s", head[optex]);
	    for (i=0; i<ITEMS(unit); i++) {
		fg = unit[i].symbol[0] == '?' ? qcol : "";
		if(optex) printf("\\multicolumn{1 %s}{l}{{%s \\tt %s}}&",
			         colors[i&1], fg, unit[i].symbol);
		else      printf("  %-14s", unit[i].symbol);
		/*if (strlen(unit[i].symbol) > 12) printf("\n    %-12s", "");*/
	    	p = unitex(unit[i].symbol, op);
		if(optex) {
		    if((got = strstr(p, " = "))) *got = 0;
		    printf("\\multicolumn{1 %s}{p}{%s %s}&", 
			          colors[i&1], fg, p);
		}
		else printf("= %-32s", p);
	    	p = unitex(unit[i].symbol, op|1);
		if (!p) p = "";
		if (optex) printf("\\multicolumn{1 %s}{l}{%s}\\\\",
			          colors[i&1], p);
		else       printf("= %s", p);
		printf("\n");
	    }
	    printf("%s", tail[optex]);
	    continue;
	}
	if (!from_stdin) printf("\n    Input: %s\n", got);
	if (optw) { 
	    if (optw&1) printf("     {nu}: %gGHz\n", unit_ghz(got));
	    if (optw&2) printf(" {lambda}: %gum\n",  unit_um(got));
	    continue;
	}
	p = unit_get1(got, &value);
	strncpy(my_unit, p, sizeof(my_unit)-1);
	printf("    Value: %.15g\n", value);
	printf("     Unit: %s\n", optex ? unitex(my_unit, 4) : my_unit);
	printf("   xpUnit: %s\n", unitex(my_unit, 0));
    	printf("  SI_unit: %s\n", unitex(my_unit, 1));
	printf("      Dim: %-10s\n", unit_dim(my_unit));

	if (value == value) {	/* True only if not NaN */
	    p = unit_si(got, &f);
    	    if (strchr(p, '?')) 
		printf(" SI_value: (?)%s\n", p);
	    else 
		printf(" SI_value: %.15g%s\n", f, p);
	}
#if 0
	if (from_stdin) {
	    printf("Value in above unit: ") ;
	    got = fgets(text, sizeof(text), stdin) ;
	    if (!got) break;
	    for (i=strlen(got)-1; (i>=0) && isspace(got[i]); i--); got[++i] = 0;
	    p = unit_get(got, text, &f) ;
	    printf("   Result: %.15e%s\n", f, p) ;
	}
#endif
    }
    exit(0);
}
#endif
