/*++++++++++++++
 Copyright:    (C) 2008-2015 UDS/CNRS
 License:       GNU General Public License
.IDENTIFICATION acut.c
.LANGUAGE       C
.AUTHOR         Francois Ochsenbein [CDS]
.ENVIRONMENT
.KEYWORDS
.VERSION  1.0   31-Oct-1991 (rmcol)
.VERSION  1.1   23-Apr-1992 (trcol)
.VERSION  1.2   27-Mar-1993 (-trim Option)
.VERSION  1.3   13-Oct-1993: % option
.VERSION  1.4   09-Nov-1993: Also align the sign in % option
.VERSION  1.5   15-Jan-1994: In numbers, allow prefix (e.g. <)
.VERSION  1.6   09-Feb-1994: Allow C-like char designation in -d parameter
			and suffix (.e.g. -d'\t' for a tab)
.VERSION  1.7   19-Apr-1994: Issue warning message if decimal point can't
				be aligned
.VERSION  1.8   03-Feb-1995: Added suffixes b and B (blank / unblank)
.VERSION  1.9   16-Jun-1995: Crashed for protection with gcc
				(due to the preplacement of a pointer,
				gcc too heavy protection !!!)
.VERSION  2.0   17-Jul-1995: Change in decimals: check first existence of .
.VERSION  2.1   23-Aug-1996: Last field: complete with blanks if width specified
.VERSION  2.2   10-Apr-1997: Added Counter
.VERSION  2.3   22-Feb-1998: -a option
.VERSION  2.4   21-Jul-1999: Truncation possibility
.VERSION  2.5   03-Feb-2000: Regular expression  -c1-12's/ //g'
.VERSION  2.6   10-Feb-2000: Accept -c. or -c= or -c meaning: copy the missing
				bytes without transformation
.VERSION  2.7   21-Mar-2000: Counter only for duplicated rows
.VERSION  2.8   10-Apr-2001: Bug (in realloc) fixed...
.VERSION  2.9   18-Jul-2001: Option -C
.VERSION  3.0   14-May-2002: Accept %10.3T with other representations of the
				decimal point .e.g.  -T , . -f1%8.3T
.VERSION  3.1   24-Jul-2002: Take care of the ^ in regexp
.VERSION  3.2   12-Jul-2004: Numbers can be sexagesimal.
.VERSION  3.3   30-Dec-2004: Accept \A \a \s \d in -T
.VERSION  3.31  16-Jan-2005: Bug fixed in fields (see V3.31)
.VERSION  3.4   10-Apr-2005: Can mix -a / -c / -f
.VERSION  3.41  15-Apr-2005: A couple of bugs... and round exponential numbers
.VERSION  3.42  15-Jun-2005: ... but now a bug in -B option!
.VERSION  3.5   24-Aug-2005: ... bug in sexa_round (communicated by H.Andernach)
				... and take care of converting blanks in TRIMtr
.VERSION  3.6   19-Sep-2005: ... another bug ! Therefore use my own edition
.VERSION  3.7   24-Oct-2005: ... limit values to some max.
.VERSION  3.8   03-Nov-2005: ... another bug in rounding (thanks Heinz!)
.VERSION  3.9   16-Nov-2005: Options Q q to quote / unquote
.VERSION  3.9   03-Jan-2006: Bug in TRIMmax (<)
.VERSION  3.91  07-Jan-2006: Issue empty lines
.VERSION  3.92  01-Feb-2007: Option -el (fill also empty lines)
.VERSION  3.93  06-May-2007: Bug -in -a option
.VERSION  3.94  04-Jun-2007: Bug if width of column smaller...
.VERSION  3.95  22-Jun-2007: Trailing blanks not removed in regexp-substitution
.VERSION  3.96  06-Jan-2008: ... trim blank: don't do if no transformation.
.VERSION  3.97  25-Feb-2008
.VERSION  3.98  20-Aug-2008: Remove the trailing blanks
.VERSION  3.99  28-Nov-2008: Added TRIMmin
.VERSION  4.0   23-Dec-2008: Better separation number/suffix
.VERSION  4.01  10-May-2009: Accept floating-point without decimals
.VERSION  4.1   19-Jun-2009: postnum changed (interprets e.g. 120/2 => 120. /2)
.VERSION  4.11  09-Dec-2009: zero-fill was not correct
.VERSION  4.12  31-Jan-2010: zero-fill was not correct in case of emtpy fields
.VERSION  4.13  23-Sep-2010: getline is now a GNU function ==> use getLine
.VERSION  4.14  09-Nov-2010: accept tail tril + regexp
.VERSION  4.15  26-Dec-2011: -b option (add blanks)
.VERSION  4.16  20-Jan-2012: accept 'f' for ROUND
.VERSION  4.2   25-Apr-2012: Accept -f-1-5 for (no sep between columns)
.VERSION  4.21  25-Jun-2014: Pb limit with number of decimals
.VERSION  4.3   27-Jun-2014: Accept limits (e.g. %9.3f!) and compressed input
.VERSION  4.31  29-Dec-2014: accept -headlines
.COMMENTS       Convert a file organized in columns.
---------------*/


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>	/* floor() function */
#ifndef HAS_FILUTIL
#define HAS_FILUTIL 0
#endif
#if HAS_FILUTIL > 0
#include <filutil.h>
#define FOPEN(f)  fzopen(f)
#define FCLOSE(f) fzclose(f)
#define VERSION 	"4.31 (27-Dec-2014, accept compressed input)"
#else
#define FOPEN(f)  fopen(f, "r")
#define FCLOSE(f) fclose(f)
#define VERSION 	"4.31 (27-Dec-2014)"
#endif

#define MAXCOL 		0x7fff
#define BUFSIZE		1024		/* Default buffer size  */
#define BUFMIN		80		/* Minimal read attempt */

#define MAX(x,y)	((x) >= (y) ? (x) : (y))
#define MIN(x,y)	((x) <= (y) ? (x) : (y))
#define ITEMS(a)	sizeof(a)/sizeof(a[0])
#define issign(c)	(((c)=='+')||((c)=='-'))
#define TRIMad	 0x0001	/* Add delimiter	*/
#define TRIMh	 0x0002	/* Remove header blanks */
#define TRIMt	 0x0004	/* Remove trail  blanks */
#define TRIMnum	 0x0008	/* Numeric field 	*/
#define TRIMd	 0x0010	/* Decimal point 	*/
#define TRIMtr	 0x0020	/* Translate 		*/
#define TRIMbl	 0x0040	/* Blank     		*/
#define TRIMdup	 0x0080	/* Unblank (duplicate)	*/
#define TRIMcnt	 0x0100	/* Count Column: 3 bits	*/
#define TRIMcnT	 0x0700	/* Count Column: 3 bits	*/
#define TRIMzf 	 0x0800	/* Zero-fill Numeric	*/
#define TRIMsgn  0x1000	/* Insert the Sign	*/
#define TRIMrnd  0x2000	/* Round Decimal Part	*/
#define TRIMado  0x4000	/* Add cnt to number 	*/
#define TRIMplus 0x8000 /* Defined with a +     */
#define TRIMsex 0x10000	/* Sexagesimal number	*/
#define TRIMseX 0x20000	/* Sexagesimal, no 0.1'	*/
#define TRIMaq  0x40000 /* Add Quality to num   */
#define TRIMaQ  0x80000 /* Add Quality to num   */
#define TRIMsub 0x100000 /* Regular exp. subst. */
#define TRIMunq 0x200000 /* Unquote a column    */
#define TRIMlad 0x400000 /* Left-adjust (fmt%-) */
#define TRIMqot 0x800000 /* Add the quotes -- not yet! */
#define TRIMmin 0x1000000 /* Minimize the number       */
#define TRIMmax 0x2000000 /* Maximize the number       */

/* How the fields are defined:
   odd numbers denote CSV fields.
*/
#define FIELDc	0	/* Field defined by -c 	*/
#define FIELDf	1	/* Field defined by -f 	*/
#define FIELDa	2	/* Field defined by -a 	*/
static int found_fields = 0;

typedef struct {	/* Specifies one operation */
	int col0;	/* First column	 (from 0)  */
	int col1;	/* Last  column  (from 0)  */
	char *text;	/* Used for -i, b or B, s <*/
	int  trim;	/* Trim indicator	   */
	char ftype;	/* Type of field FIELDx	   */
	short width;	/* If specified by %	   */
	char ndec;	/* Number of decimals	   */
	char wcnt;	/* Width of Counter	   */
	char echar ;	/* Ending char for regexp  */
	char dpoint ;   /* Decimal point (. or ,)  */
	int cnt ;	/* Counter value	   */
	char *cexp ;	/* Compiled reg expression */
  } FIELD;
static FIELD *ff = (FIELD *)0;
static char sexa_sep = ' ';
static char dpoint = '.'; /* Used in decimals */
static int nf, nfa = 0;	/* nfa = Allocated fields  */
static int  uppercut ;	/* Previous upper limit	   */
static int  maxf   = 0;	/* Upper value of column   */
static char delim[]  = "\t";
static char *comment = "" ;
static int headlines = 0;
static int comlen = 0 ;
static char *trt;	/* For Translate */
static int  lineno = 0;
static char *infile = (char *)0;
static char *next ;	/* Next line (for counts)  */
static char dec_overflow[20];	/* see edit_dec	   */

static char tofill ;	/* Flag set by -c= or -c.  */
static char opte = 0;	/* Option -e */

/* Use buffers to keep data */
typedef struct { int allocated ; char *buf; } ABUF ;

/*
#define	of  (split&SPLITf)
#define	oc  (split&SPLITc)
*/

static char pgm[] = "acut";
static char usage[] = "\
Usage: %s [-Cc] [-headlines=n] [-dc] [-el] [-T s1 s2] \n\
       [-a_cols_|-bn|-c_cols_|-f_fields_|-i_text_|-rn _text_]...\n\
       [file...]\n";

static double rounding[] = {
   5.e-1,  5.e-2,  5.e-3,  5.e-4,  5.e-5,  5.e-6,  5.e-7,  5.e-8,
   5.e-9, 5.e-10, 5.e-11, 5.e-12, 5.e-13, 5.e-14, 5.e-15, 5.e-16
} ;

static double fact10[] = {
   1.,    10.,    100.,  1000., 1.e4,  1.e5,  1.e6,  1.e7,
   1.e8,  1.e9,  1.e10,  1.e11, 1.e12, 1.e13, 1.e14, 1.e15
};

#define Malloc(nb)	malloc(nb) \
			/*, printf("\n----Allocated %d bytes\n", nb) */
#define PutChar(c)	putchar(c)

double atof();

/*==================================================================
		Internal Routines
 *==================================================================*/
static void error(t0, t1)
/*++++++++++++++++
.PURPOSE  Error exit
.RETURNS  -
-----------------*/
  char *t0;	/* IN: First part of text  */
  char *t1;	/* IN: Second part of text */
{
    if (t0)   fprintf(stderr, "#*** %s: %s%s\n", pgm, t0, t1);
    fprintf(stderr, usage, pgm);
    exit(1);
}

static void warning(t0, t1)
/*++++++++++++++++
.PURPOSE  Issue warning message
.RETURNS  ---
-----------------*/
  char *t0;	/* IN: First part of text  */
  char *t1;	/* IN: Second part of text */
{
    fprintf(stderr, "++++");
    if (infile) fprintf(stderr, "File %s, ", infile);
    fprintf(stderr, "line#%d: ", lineno);
    if (t0) fprintf(stderr, "%s <%s>\n", t0, t1);
}

static void help()
/*++++++++++++++++
.PURPOSE  Display help, then exit
.RETURNS  ---
-----------------*/
{
  static char text[] = "\
      -Cc: Defines a comment char -- lines starting by 'c' a just copied\n\
      -dc: Define the Delimiter char\n\
      -el: empty lines processed (by default, empty lines are just copied)\n\
   -acols: Add delimiters where a comma appears in cols\n\
   -bn   : Add n times a space character (blank)\n\
   -ccols: Copy text from input to output with specified transformation\n\
 -ffields: Copy text from fields within delimiters\n\
 -headlines: specifies how many lines belong to header (comments)\n\
   -itext: Insert the specified text (an empty text inserts the delimiter)\n\
   -rn tx: Repeat the text 'tx' n times (therefore -bn identical to -rn ' ')\n\
 -T s1 s2: define translation from s1 into corresponding char of s2\n\
	   (accept \\a and \\A for lowercase/uppercase alphabet) \n\
\n\
 _cols_ and _fields_  are specified with commas and dashes, e.g.\n\
       1,12  for cols 1 and 12 \n\
       8-12  for cols 8 thru 12\n\
       8+5   for cols 8 thru 12\n\
       8+5*3 for 3 times, equivalent to  8-12,13-17,18-22\n\
             (missing numbers mean: next col - up to end;\n\
	      no value at all means: all missing cols)\n\
       ...   for 'all columns not yet mentioned'\n\
 There are the following possible terminators:\n\
   a+/- = add a constant (integer) number to the column\n\
   < = limit the value to some top value    (e.g. <99.99)\n\
   > = limit the value to some bottom value (e.g. >-9.99)\n\
   b = blank zone if identical to previous record\n\
   B = replace blank zone by previous record\n\
   h = l = remove head blanks = left alignment\n\
   j = justify (remove head & tail blanks)\n\
   n = numeric field (the limit indicators < > ~ = are pushed to the left)\n\
   q = unquote a column (remove the starting/ending quotes \")\n\
   Q = Quote a column (enclose in quotes \" with SQL conventions)\n\
   t = r = remove tail blanks = right alignement\n\
   T = translate\n\
   s/reg_exp/subst/g = substitute (like sed)\n\
   +n[a|A|d|x|o] = Edit a counter of width n in alpha/decimal/hexa/octal\n\
                   when field identical to previous line\n\
   %_w_[[.:,]_d_[rsq]] applies on NUMERIC columns, and specify the output\n\
    format [decimal-point alignement]\n\
       _w_=width of column, may include + (sign) and 0(zero-fill);\n\
       _d_=number of decimals following the fraction mark;\n\
       's' indicates a sexagesimal number\n\
       'S' sexagesimal, with transformation of decimal h/min into min/sec\n\
       'r' (or 'f') asks to ROUND the number if there are too many decimals;\n\
           followed by the exclamation (!) also limit between -9... and 99...\n\
       'q' asks to add a Quality from 'a' (all decimals present)\n\
       'Q' asks to add a Quality from 'A' (all decimals present)\n\
" ;

    fprintf(stdout, usage, pgm);
    fprintf(stdout, "\n%s", text);
    exit(0);
}

static int thebyte(ins)
/*++++++++++++++++
.PURPOSE  Interpret a string according to C standards
.RETURNS  Number of bytes in out
.REMARKS  \octal or \f \b \r \n ...
-----------------*/
  char *ins;       /* IN: String to interpret */
{
  int o;

    if (*ins != '\\') return(*ins);
    switch(ins[1]) {
      case 'b': return('\b');
      case 'f': return('\f');
      case 'n': return('\n');
      case 'r': return('\r');
      case 't': return('\t');
      case 'e': case 'E': return(033);
      default:
	if (isdigit(ins[1]))
	    for (o=0, ++ins; isdigit(*ins); ins++) o = o*8 + (*ins-'0');
	else o = ins[1];
	return(o);
    }
}

/*==================================================================*/
#if 0
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);
}
#endif

static int decimals(str, len)
/*++++++++++++++++
.PURPOSE  Compute number of decimals
.RETURNS  Number of existing decimals / -1
.REMARKS  Just the index, right to left, of decimal point
		defined in dpoint.
-----------------*/
  char *str;	/* IN: String to  scan  */
  int len ;	/* IN: Length of string */
{
#if 1
  char *p ;
    for (p=str+len-1; (p>=str)&&(*p != dpoint) ; p--) ;
    if (p < str) return(-1) ;
    return(len-(p-str)-1) ;
#else
  char *p, *e;
    for (p=str, e=p+len; (p<str)&&(*p != dpoint) ; p++) ;
    if (p>=str) return(-1);
    for (str = ++p; (p<e) && isgraph(*p); p++) ;
    return(p-str);
#endif
}

static int sexa_dec(str, len)
/*++++++++++++++++
.PURPOSE  Compute number of decimals in sexagesimal
.RETURNS  -5 (nothing) -4 (degrees) ... 0 (seconds) ...
.REMARKS  sexa_sep (separator between sexagesimal components) set.
-----------------*/
  char *str;	/* IN: String to  scan  */
  int len ;	/* IN: Length of string */
{
  char *p, *e;
  int nd = -5;
  int k,j;

    p=str, e=p+len;
    if (issign(*p)) p++;
    while ((p<e) && (*p == ' ')) p++;
    if (p>=e) return(nd);

    sexa_sep = ' ';
    for (k=0; k<3; k++) {
        /* Check there is a number */
        if (isdigit(*p)) nd += k ? 2 : 1 ;
        else return(nd);
        for (j=0; (p<e) && isdigit(*p) && (j<2); p++) j++;
        if (p>=e) return(nd);

        /* Verify there are decimals */
    	if (*p == '.') {
	    for (++p; (p<e) && isdigit(*p); p++) nd++;
	    return(nd);
	}
	if (k&2) return(nd);	/* Last component!  */

	/* Skip the separation between the components */
	if (*p == ':') sexa_sep = *(p)++;
	else if (isdigit(*p)) sexa_sep = 0;	/* No separation at all... */
	while ((p<e) && (*p == ' ')) p++;
	if (p>=e) return(nd);
    }
    return(nd);
}

static int edit_dec(char *buf, double value, int width, int ndec)
/*++++++++++++++++
.PURPOSE  Edit (with truncation) a decimal number
.RETURNS  0 if OK, negative number of width too small
	In this case, the leftmost part added in the static dec_overflow.
-----------------*/
{
  char sign;
  double part[2];
  int4 ival;
  int i;
    if (value >= 0) sign = 0;
    else { sign = '-'; value = -value; }
    part[0] = floor(value) ;	/* Integer part */
    part[1] = value - part[0];	/* Fractional   */
    if (ndec>0) part[1] *= fact10[ndec];

    i = width;
    for (ival=part[1]; ndec>0; ndec--) {
	buf[--i] = (ival%10) + '0';
	ival /= 10;
    }
    if (ndec>=0) buf[--i] = '.';
    for (ival=part[0]; (i>0) && ival; ival /= 10)
	buf[--i] = (ival%10) + '0';

    /* Add the 0. if edited . */
    if ((buf[i] == '.') && (i>0)) buf[--i] = '0';

    /* Add the sign */
    if (sign && (i>0)) { buf[--i] = sign; sign = 0; }

    /* Blank fill */
    while (i>0) buf[--i] = ' ';

    /* Verify: overflow ? */
    if (ival || sign || (i<0)) {
	if (sign) { dec_overflow[0] = sign; sign = 1; }
	sprintf(dec_overflow+sign, "%d", ival);
	i -= strlen(dec_overflow);
    }
    return(i);
}

static int sexa_round(str, result, width, ndec)
/*++++++++++++++++
.PURPOSE  Round a sexagesimal number to the number of specified decimals
.RETURNS  Width used by degrees/hours
.REMARKS  wdth > ndec+7
-----------------*/
  char *str;	/* IN: Sexagesimal number to round */
  char *result;	/* OUT: Rounded number 		*/
  int width;	/* IN: width to use in result	*/
  int ndec;	/* IN: Number of decimals	*/
{
  char sep = ' ';
  char *p, *r, *s;
  int h,m, nh;
  double f;

    /* Find in str the fraction */
    for (p=str+strlen(str)-1; (p>str) && (*p == ' '); p--) ;
    s = p;	/* Last non-blank char of number */
    while ((p>str) && isdigit(*p)) p--;
    if ((p>str) && (*p == '.'))  {
	--p;
	if ((p>str) && isdigit(*p)) --p;
	if ((p>str) && isdigit(*p)) --p;
        /* Now p->separation char e.g. last ':' in 23:12:01.12 */
    }
    else {	/* added V3.43 */
	if ((p == str)  && (*p == ' ')) {	/* Totally empty */
	    return(0);
	}
	if (ispunct(*p) || isspace(*p)) ;	/* Separator m:s exists */
	else if (isdigit(*s)) {			/* No separator at all  */
	    p = s;
	    if (p>str) p--;
	    if (p>str) p--;
	}
	else return(0);		/* Can't do anything! */
    }
    f = atof(p+1) + rounding[ndec];
    h = m = nh = 0;
    if (f >= 60.) { f -= 60.; m++; }

    /* Get the other components */
    if (isdigit(*p)) sep = 0;
    else {	/* Accept blank of colon as separators */
        while ((p>str) && (*p == ' ')) p--;
        if (*p == ':') sep = *p;
    }
    /* Get the minutes */
    if (*p == sep) --p;
    p[1] = 0;			/* Be sure the number is terminated */
    if ((p>str) && isdigit(*p)) --p;
    if ((p>str) && isdigit(*p)) --p;
    m += atoi(p+1); if (m >= 60) { m -= 60; h++; }
    /* Get the hours */
    while ((p>str) && (*p == ' ')) p--;
    if (*p == sep) --p;
    p[1] = 0;			/* Be sure the number is terminated */
    while ((p>=str) && isdigit(*p)) nh++, p--; h += atoi(p+1);
    s = p;	/* Position where prefixes (e.g. sign) exist */

    /* Write out the result */
    r = result + width - ndec -3;
    result[width] = 0;
    edit_dec(r, f, ndec+3, ndec) ;	/* Write seconds */
    if (*r == ' ') *r = '0';
    if (sep) *--r = sep;
    *--r = '0' + m%10;
    *--r = '0' + m/10;
    if (sep) *--r = sep;
    m = r-result;
    *--r = '0' + h%10;	h /= 10; --nh;	/* Last digit of hours */
    while ((r>result) && (nh>0)) {
	nh--;
	*--r = '0' + (h%10);
	h /= 10;
    }
    /* Verify there is enough room with the prefix   */
    for (p=str; (*p == ' ') && (p<=s); p++) ;
    nh = (s-p)+1;	/* Prefix */
    if ((h>0)||((r-result)<nh)) fprintf(stderr,
	"++++Can't align <%s>****\n", str);
    /* Complete from left to right (changed V3.5) */
    while (nh>0) {
	if (result<r) *(result++) = *(s++);
	nh--;
    }
    /* Complete with blanks */
    while (result<r) *(result++) = ' ';

    return(m);

}

#if 0
static char *bounded_string(char *str, FIELD *pf)
/*++++++++++++++++
.PURPOSE  Apply the min/max on a field
.RETURNS  The bounded value / str
-----------------*/
{
  double val, *aval;
  int k;
		/* Bound the number */
    if (!(pf->trim&(TRIMmax|TRIMmin))) /* No bounding... */
	return(str);
    val = atof(str);
    aval = (double *)pf->cexp;
    if      ((pf->trim&TRIMmin) && (val < aval[0]))
	k = 0;
    else if ((pf->trim&TRIMmax) && (val > aval[1]))
	k = 1;
    else return(str); 			/* No bounding */
    return(pf->text + pf->cexp[2*sizeof(double)+k]);
}
#endif

static int isBlank(text, len)
/*++++++++++++++++
.PURPOSE  Check whether a field is filled with blanks
.RETURNS  1 (blank) / 0 (notblank)
.REMARKS  The NUL (end-of-record) is accepted.
-----------------*/
  char *text;	/* IN: String to interpret */
  int  len;	/* IN: Length to scan	   */
{
  char *p, *e;
    for (p=text, e=text+len;  (p < e) && (*p == ' ') ; p++ ) ;
    if (p==e) return(1) ;
    if (*p) return(0) ;
    return(1);
}

static int getstring(char *in, char *out, int osize)
/*++++++++++++++++
.PURPOSE  Interpret a string according to C standards +
	Extensions \s \w \d \x \o \a \A
.RETURNS  Number of bytes in out
.REMARKS  \octal or \f \b \r \n ...
-----------------*/
  /* IN:  in = String to interpret */
  /* OUT: out = Result of interpretation */
{
  char *p, *q, *e, *a, c; int o;  c = 0177;

    for (p = in, q = out, e=q+osize-1; *p && (q<e); p++, q++) {
	if ((*p == '-') && (c < p[1])) {
		for (++p; ++c <= *p; q++)  *q = c;
		c = 0177;
		--q;
		continue;
	}
	if (*p == '\\') switch(*++p) {
	    case 'b': *q = '\b'; continue;
	    case 'f': *q = '\f'; continue;
	    case 'n': *q = '\n'; continue;
	    case 'r': *q = '\r'; continue;
	    case 't': *q = '\t'; continue;
	    case 'E':
	    case 'e': *q = 033; continue;
	    case 'a': a = "abcdefghijklmnopqrstuvwxyz";
		goto case_pattern;	/* lowercase */
	    case 'A': a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
		goto case_pattern;	/* uppercase */
	    case 'd': a = "0123456789";
		goto case_pattern;	/* digits */
	    case 'w': a =
	      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789";
		goto case_pattern;	/* alphanum */
	    case 's': a = " \t\v\n\r\f";
		goto case_pattern;	/* spaces */
	    case_pattern:
	       while (*a && (q<e)) *(q++) = *(a++);
	       --q;			/* incremented at loop top */
	       continue;
	    default:
		if (isdigit(*p)) {
		    o = 0;
		    while (isdigit(*p)) o = o * 8 + (*p - '0');
		    *q = o;
		    p--;
		    continue;
		}
	}
	*q = c = *p;
    }
    if (q<e) *q = 0;
    else *e = 0;
    return (q - out);
}

/*==================================================================
		Regular Expressions
 *==================================================================*/

static char *pregexp ;	/* Shared with compile (regexp.h) */
#define INIT
/* #define INIT		register char *pregexp = instring ; */
#define GETC()		*(pregexp++)
#define PEEKC()		(*pregexp)
#define UNGETC(c)	(--pregexp)
#define RETURN(c)	return(c);
#define	ERROR(c)	fprintf(stderr, \
			"\n****acut: Error #%d in regexp '%s'+%d\n",\
			c,instring,(int)(pregexp-instring))
#include <regexp.h>

#if 0
int strexp(cexp, input)
/*++++++++++++++++
.PURPOSE  Check whether input satisfies the compiled regular expression
.RETURNS  Number of matched chars / 0 for mismatch
-----------------*/
  char *cexp;	/* IN: Compiled regular expression */
  char *input;	/* IN: String to check	*/
{
  int saved_circ, n;
    saved_circ = circf ;
    circf = 1 ;
    if (step(input, cexp)) n = (loc2-loc1);
    else n = 0;
    circf = saved_circ ;
    return(n);
}
#endif

static char *compile_expr(input)
/*++++++++++++++++
.PURPOSE  Compile a regular expression
.RETURNS  The (newly allocated) compiled expression / NULL
-----------------*/
  char *input;	/* IN: The regular expression i.e. /exp/ */
{
  char buf[BUFSIZ] ; char *p, echar; int n;
    memset(buf, 0, sizeof(buf));
    echar = *(input++) ;
    pregexp = input ;
    p = compile(input, buf, buf+sizeof(buf), echar);
    /* Note: bytes scanned in compilation are pregexp - input */
    n = (p-buf) +1 ;
    p = Malloc(n+1);
    p[0] = circf;	/* 1st byte of compiled regexp = ^ */
    memcpy(p+1, buf, n);
    return(p) ;
}

static int regsub(cexp, arg, end_char, input)
/*++++++++++++++++
.PURPOSE  Substitute a string (like sed), and writes the output
.RETURNS  Number of characters written out
-----------------*/
  char *cexp;	/* IN: Compiled regular expression */
  char *arg;	/* IN: Substitution String	*/
  int  end_char;/* IN: Character terminating arg*/
  char *input;	/* IN: String to transform	*/
{
  int saved_circ, n, i, output_bytes;
  char *a ;
    saved_circ = circf ;
    circf = *(cexp++) ;
    loc2 = input ;
    for (output_bytes = 0 ; step(input, cexp); n++, input = loc2) {
	n = loc1-input ;
	fwrite(input, 1, n, stdout); /* PutText(input, n) ; */
	output_bytes += n ;
	for (a = arg; *a && (*a != end_char); a++) {
	    if (*a != '\\') { PutChar(*a); output_bytes++; continue; }
	    ++a ;
	    if (isdigit(*a)) {
	        i = *a-'1' ;
	        if ((i >= 0) && (i < nbra)) {
		    n = braelist[i] - braslist[i] ;
		    fwrite(braslist[i], 1, n, stdout);
		    /* PutText(braslist[i], n) ; */
		    output_bytes += n ;
		    continue ;
	        }
	    }
	    if (!*a) { PutChar('\\') ; output_bytes++; continue ; }
	    PutChar(*a) ; output_bytes++;
        }
	if (*a != end_char) fprintf(stderr, "\n****acut: Error in s/%s/\n",
	    arg) ;
	if (*a) a++ ;
	if (*a != 'g') break ;
    }
    n = strlen(loc2); output_bytes += n ;
    fwrite(loc2, 1, n, stdout); /* PutString(loc2) ; */
    circf = saved_circ ;
    return(output_bytes);
}



/*==================================================================*/
static int prenum(str, len)
/*++++++++++++++++
.PURPOSE  Count the symbols preceding a number
.RETURNS  Number of symbols
-----------------*/
  char *str;	/* IN: string to scan	*/
  int len ;	/* IN: length of string	*/
{
  char *p, *e;
    for (p=str, e=p+len ; p<e ; p++) {
    	if (issign(*p)) return(p-str) ;
    	if (isdigit(*p)) return(p-str) ;
    	if (*p == '.') return(p-str) ;
    }
    return(p-str) ;
}
static int postnum(str, len, from_end)
/*++++++++++++++++
.PURPOSE  Count the symbols following a number
.RETURNS  Number of symbols
-----------------*/
  char *str;	/* IN: string to scan	*/
  int len ;	/* IN: length of string	*/
  int from_end;	/* IN: non-zero if looking from end */
{
  char *p, *e;
    if (from_end) for (p=e=str+len-1 ; p>=str ; p--) {
    	if (isdigit(*p)) return(e-p) ;
    	if (*p == '.') return(e-p) ;
    }
    else {
        for (p=str, e=p+len; (p<e) && (*p == ' '); p++) ;
        if (p==e) return(0);
        if (issign(*p)) p++;
        while ((p<e) && isdigit(*p)) p++;
        if (p==e) return(0);
        if (*p == '.') for (++p; (p<e) && isdigit(*p); p++) ;
        if (isalpha(*p) && ((e-p)>1)) {  /* exponent format ? */
	    if ((tolower(*p) == 'e') && ((isdigit(p[1]) || issign(p[1]))))
	        for (p += 2; (p<e) && isdigit(*p); p++) ;
        }
    }
    return(e-p) ;
}

static int edit_counter(format, width, count)
/*++++++++++++++++
.PURPOSE  Edit the Counter
.RETURNS  Number of output characters
-----------------*/
  int format ; 	/* IN: TRIM_cntxx */
  int width ;	/* IN: How many bytes */
  int count ; 	/* IN: Value to edit  */
{
  char buf[20], *e ;
  static int  bases[] = { 2 , 10, 27,  27,   16, 16,  8,  8 } ;
  static char zeros[] = {'0','0','A'-1,'a'-1,'0','0','0','0' } ;
  int base, value, i, c, zero, bytes;
/* printf ("....edit_counter(%04X, %d, value=%d)\n",format,width,count) ; */
    i = (format>>8) ;
    base = bases[i] ; zero = zeros[i] ;
    value = count ;
    if (zero != '0') 	/* The zero doesn't exist. Transforms 0 into 1 */
        for (c = base; value >= c ; c *= base)
	value += (c/base) + (value-c)/(c-c/base) ;
    e = buf + sizeof(buf), i=0;
    while(value) {
    	c = zero + (value%base) ;
    	if ((base == 16) && (!isxdigit(c))) c += ('A'-1) - 9 ;
    	if (format&TRIMcnt) c = tolower(c) ;
    	e[--i] = c ;
	value /= base ;
    }
    bytes = 0 ;
    for (c=width+i; --c >= 0; bytes++) {	/* Leading Blanks */
	/* if ((count == 0) || (base == 26)) putchar(' ') ;
    	 * else putchar(zero) ;
	 */
	putchar(' ') ;
    }
    while(i<0)
    	c = e[i++], putchar(c), bytes++ ;
    return(bytes) ;
}

/*==================================================================*/
static int getabuf(in, abuf)
/*++++++++++++++++
.PURPOSE  Get a complete line
.RETURNS  Number of bytes read -- 0 for a Comment Line
.REMARKS  Result in the line buffer
-----------------*/
  FILE *in; 	/* IN: File to read */
  ABUF *abuf ;	/* MOD: Buffer */
{
   char *pc, *pe, eol;

    if (!abuf->allocated) {
	abuf->allocated = BUFSIZE;
	abuf->buf = Malloc(abuf->allocated);
    }
    pc = abuf->buf ;
    if (!pc) perror("****acut: getLine") , error("Can't get Memory?","") ;
    pe = pc + abuf->allocated - 4; eol = 0; *pc = 0;

#ifdef DEBUG
    printf("....Reading in buffer of %d bytes: %p\n",
      abuf->allocated, abuf->buf, abuf->buf);
#endif

    while (!eol) {
	if ( (pe - pc) < BUFMIN) {
#ifdef DEBUG
	    printf("....Need to reallocate(%d bytes) at @%p:\n",
	    	abuf->allocated, abuf->buf, abuf->buf);
#endif
   	    abuf->allocated += BUFSIZE;
   	    abuf->buf = realloc(abuf->buf, abuf->allocated);
#ifdef DEBUG
	    printf("++++Line reallocated+ (%d bytes) at @%p:\n",
	    	abuf->allocated, abuf->buf, abuf->buf);
#endif
   	    pc = abuf->buf + strlen(abuf->buf);
	    pe = abuf->buf + abuf->allocated - 4;
    	}
    	if (fgets(pc, pe-pc, in)) {
    	    pc += strlen(pc);
    	    eol = (pc[-1] == '\n');
    	}
    	else if (pc == abuf->buf)  return(-1);  /* EOF */
    	else break;
    }
    *--pc = 0;		/* Remove the \n */
    if (pc>abuf->buf) {	/* V3.98: Remove \r */
	if (*--pc == '\r') *pc = 0;
	else ++pc;
    }
#ifdef DEBUG
    printf("====Read <%s>\n", abuf->buf);
#endif
    return(pc - abuf->buf);
}

static char *getLine(in)
/*++++++++++++++++
.PURPOSE  Get a complete line
.RETURNS  The string read
.REMARKS  Result in the line buffer
-----------------*/
  FILE *in; 	/* IN: File to read */
{
   static ABUF abuf[2] ;	/* Keep two buffers */
   static int  reclen = 0 ;	/* Length of previous record */
   static int  ibuf = 0 ;
   char *line ;
   int n ;

    if (lineno == 0) {		/* Initial: read 2 line */
	ibuf = 0 ; next = (char *)0 ;
	n = getabuf(in, abuf) ;
	if (n < 0) ibuf = -1 ;		/* Indicates EOF */
	else reclen = n ;
    }
    if (ibuf < 0) return((char *)0) ;	/* End of File */

    line = abuf[ibuf].buf ;		/* To return   */
    next = (char *)0 ;
    lineno++ ;

    /* Read the Next line */
    ibuf ^= 1 ;
    n = getabuf(in, &abuf[ibuf]) ;
    if (n < 0) ibuf = -1 ;
    else {	/* Next line: if shorter fill with zero */
	next = abuf[ibuf].buf ;		/* Next Line	*/
	if (n < reclen) {
	    if (reclen >= abuf[ibuf].allocated)
		reclen = abuf[ibuf].allocated-1;
	    memset(next+n+1, 0, reclen-n) ;
	}
	reclen = n ;
    }
    return(line) ;
}

static char *not_yet_used()
/*++++++++++++++++
.PURPOSE  Create the list of non-used columns
.RETURNS  The full text
-----------------*/
{
  FIELD *f ;
  char buf[20] ;
  char *flags, *edited, *ped ;
  int allocated, i, j ;

     flags = Malloc(maxf) ;
     memset(flags, 0, maxf) ;
     for (f=ff, i=nf; --i>=0; f++) {
     	for (j=f->col0; j<MIN(maxf,f->col1); j++) flags[j] = 1 ;
     }

     allocated = 256; edited = ped = Malloc(allocated+2) ;
     *edited = 0 ;
     for (i=0; i<maxf; i++) {
     	if (flags[i]) continue ;
     	if (*edited) *(ped++) = ',' ;
     	sprintf(buf, "%d", 1+i) ;
     	for (j=i; (j<maxf) && (!flags[j]); j++) ; j-- ;
     	if (j > i) sprintf(buf+strlen(buf), "-%d", j+1) ;
     	i = j ;
     	if (((ped-edited) + (int)strlen(buf)) > allocated) {
     	    j = (ped-edited) ;
     	    allocated += 256 ;
     	    edited = realloc(edited, allocated+2) ;
     	    ped = edited + j ;
     	}
     	strcpy(ped, buf) ; ped += strlen(ped) ;
     }

     free(flags) ;
     return(edited) ;
}

/*==================================================================*/
static FIELD *fields(string, ftype)
/*++++++++++++++++
.PURPOSE  Interpret a set of fields
.RETURNS  Last allocated field
-----------------*/
  char *string;	/* IN: ascii text to interpret 	*/
  int  ftype;	/* IN: FIELDa / FIELDc / FIELDf */
{
  int  nc, isok, times, trim, trimad, i;
  char *p, afill[40];
  FIELD *fp, *f1, *fe;
  double val;

    /* Inform about the mixture of fields */
    found_fields |= ftype;
    /* Fill the field defined previously, e.g. -c= -c9- */
    if (tofill) {
        tofill = 0 ;
        nc = atoi(string) ;
	if (uppercut < nc-1) {
	    sprintf(afill, "%d-%d", uppercut+1, nc-1) ;
	    fields(afill, ftype) ;
	}
    }
    /* Count the number of Fields. */
    i = 0;					/* Number of commas */
    for (nc = 1, p = string; *p; p++) {
	if (*p == 's') break ;			/* Substitute ends up ! */
	if (*p == '*') times = atoi(p+1)-1 ;	/* Repetition */
	else if (*p  == ',') { i++; times =1 ; }
	else continue ;
	if (times < 1) times = 1 ;
	nc += times ;
    }
    if (ftype == FIELDa) nc += i;	/* Each , means: "Add a delimiter */
    if ((nc+nf) > nfa) {
  	nfa = (nc+nf+15)&0xfff0;	/* Multiple of 16 */
  	if (ff) ff = (FIELD *)realloc(ff, nfa*sizeof(FIELD));
  	else    ff = (FIELD *)Malloc (nfa*sizeof(FIELD));
    }

    if (*string == '.') {	/* All columns not yet mentioned */
    	if (nc != 1)  error("all non-mentioned columns too complex: ", string) ;
    	return(fields(not_yet_used(), ftype)) ;
    }

    /* Interpret the string	*/

    f1 = fe = fp = ff + nf; 	/* Note: next initialisation required, it can
  			   	   happen that the loop is not executed */
    memset(fp, 0, sizeof(FIELD)); fp->col1 = -1;

    times  = 0 ;
    trimad = ftype == FIELDf ? TRIMad : 0; /* V4.2: add delimiter if , */
    /* V4.2: no separator between fields */
    p = string; 
    if ((*p == '-') && isdigit(p[1])) { p++; trim = TRIMplus; }
    else trim = 0;
    for ( ; *p; fp++) {
    	fe = fp;		/* Last Field with trim explanations */
    	nf++ ;
	memset(fp, 0, sizeof(FIELD)); fp->ftype = ftype; fp->trim = trim; 
    	fp->col0 = uppercut; fp->col1 = -1;
	if (isdigit(*p)) {
	    fp->col0 = atoi(p) -1;
	    while(isdigit(*p)) p++;
	}
	uppercut = fp->col1 = fp->col0 + 1;
	isok = 0 ;	/* No upper limit yet	*/
	if (fp->col1 > maxf) maxf = fp->col1;
	if (*p == '+') {
	    isok = *(p++) ;
	    uppercut = fp->col0 + atoi(p);
	    trimad = 0;			/* No addition of delimiter */
	    fp->trim |= TRIMplus;	/* Added V4.2 */
	}
	else if (*p == '-') {
	    isok = *(p++) ;
	    uppercut = atoi(p);
	}
	if (isok) {
	    fp->col1 = uppercut ;
	    if (fp->col1 <= 0) fp->col1 = MAXCOL;
	    else if (fp->col1 > maxf) maxf = fp->col1;
	    if (fp->col1 <= fp->col0) {
		fprintf(stderr, "    %s\n    ", string);
		while(string < p) fputc(' ', stderr), string++;
		fprintf(stderr, "^\n");
		error("Invalid column range", "");
	    }
	    while(isdigit(*p)) p++;
	}
	if (*p == '*') {		/* Repetition	*/
	    p++;
	    times = atoi(p) ;
	    while (isdigit(*p)) p++ ;
	    while (times > 1) {
	    	fp[1] = fp[0] ;
		fp[1].col0 = uppercut ;
		uppercut += (fp[0].col1 - fp[0].col0) ;
		fp[1].col1 = uppercut ;
		fp++ ; nf++; times-- ;
	    }
	}

	if(*p == ',') {			/* A next range */
#if 0
	    if (ftype == FIELDf) 	/* In -f option	*/
		fp->trim |= TRIMad;
#else	    /* V4.2 */
	    fp->trim |= trimad;
#endif
	    if (ftype == FIELDa) {	/* In -a option	*/
	    	/* -a1,12,15- is equivalent to -a1-11,12-14,15-	*/
	    	if ((!isok)&&(isdigit(p[1]))) {
	    	    uppercut = atoi(p+1) - 1;
	    	    if (uppercut > fp->col0) fp->col1 = uppercut ;
	    	}
	    	fp++ ; nf++ ;
	    	fp->col0 = fp->col1 = -1;
	    	fp->text = delim ;
	    }
	    p++;  continue;
	}
	break;
    }

    /* Look for Qualifiers */
    if (*p == '%') {	/* A Format is provided... */
    	p++;
	fe->trim &= ~TRIMlad;	/* No left-adjustment */
	fe->trim |= TRIMt;	/* Numeric field */
    	if (*p == '-') { 	/* Negative width = left-adjustment */
    	    fe->trim &= ~(TRIMnum|TRIMt);
    	    fe->trim |= (TRIMh|TRIMlad);
    	    p++;
        }
	if (*p == '+')  			/* Add Sign  */
	    fe->trim |= TRIMsgn, p++ ;
	if (*p == '0') fe->trim |= TRIMzf ;	/* Zero-fill */
    	fe->width = atoi(p); while(isdigit(*p)) p++;
    	if ((*p == '.')||(*p == ':')||(*p == ',')) {
    	    fe->trim |= TRIMh|TRIMt|TRIMnum;
	    if (*p == '.') {
		fe->trim |= TRIMd ;
	    }
    	    p++;
    	    fe->ndec = atoi(p);
    	    while(isdigit(*p)) p++;
    	}
	/* Additional options */
	for (; isgraph(*p); p++) {
	    switch(*p) {
	      case 'q':	/* Add Quality */
	        fe->trim |= TRIMaq;
	        continue;
	      case 'Q':	/* Add Quality */
	        fe->trim |= TRIMaQ;
	        continue;
	      case 's': 	/* Sexagesimal */
	        fe->trim &= ~TRIMh;			/* Keep left blanks */
	        fe->trim |= TRIMsex;
	        continue;
	      case 'S': 	/* Sexagesimal -- transform 0.1' into " */
	        fe->trim &= ~TRIMh;			/* Keep left blanks */
	        fe->trim |= TRIMsex|TRIMseX;
	        continue;
	      case 't': case 'r': case 'f':
	        fe->trim |= TRIMrnd ;
    	        if (fe->ndec>=ITEMS(rounding))
		    error("Rounding too many decimals", string);
		if((p[1] == '!') && (fe->trim&TRIMnum) && (fe->ndec>=0)
		   && (fe->width<sizeof(afill)-5)) {
		   /*&& (fe->width>(fe->ndec+2)) */
		    /* V4.3: accept r! => bound */
		   memset(afill, '9', sizeof(afill));
		   p = afill; *(p++) = 'r'; *(p++) = '>';
		   p[0] = '-'; p[fe->width-1-fe->ndec] = '.';
		   p += fe->width; *(p++) = '<';
		   p[fe->width-1-fe->ndec] = '.';
		   p[fe->width] = 0;
		   p = afill;
		}
	        continue;
	    }
	    break;
	  /* Accept all other options
	      default:
	    fprintf(stderr, "****acut: Bad char '%c' in %s\n", *p, string);
	    error((char *)0, (char *)0);
	  */
	}
    }

    isok = 1;
    while(isok && isalpha(*p)) switch(*(p++)) {
      case 'a':	/* Add offset */
	if (fe->trim&TRIMcnT)
	    error("Can't have counter and addition: ",  p-1) ;
	fe->trim |= (TRIMado) ;
	fe->cnt = atoi(p) ;
	if (issign(*p)) p++ ;
	while(isdigit(*p)) p++ ;
	continue ;
      case 'l':
      case 'h':	/* Trim */
    	fe->trim &= ~(TRIMnum|TRIMt);
	fe->trim |= TRIMh;
	continue;
      case 'j':	/* Trim */
	fe->trim |= TRIMh|TRIMt;
	continue;
      case 'n':	/* Numeric */
	fe->trim |= TRIMnum|TRIMh|TRIMt ;
	continue ;
      case 'q':	/* Unquote */
	fe->trim |= TRIMunq;
	continue ;
      case 'r': case 'f':
      case 't':	/* Trim */
	fe->trim |= TRIMt;
	continue;
      case 'T':	/* Translate */
        if (!trt) error("-T definition missing in", string);
	fe->trim |= TRIMtr; fe->text = trt;
	/* Signal changed decimal point */
	if ((fe->trim & TRIMd) && (trt['.'] == '.')) {
	    for (i=0; (i<256) && (trt[i] != '.'); i++) ;
	    if (i<256) fe->dpoint = i;
	}
	continue;
      case 'b':	/* Blank field if identical to preceding */
	fe->trim |= TRIMbl;
	if (ftype != FIELDc)
	    error("-b allowed only in -c mode:", string);
	continue;
      case 'B':	/* Unblank if blank from preceding record  */
	fe->trim |= TRIMdup;
	if (ftype != FIELDc)
	    error("-B allowed only in -c mode:", string);
	continue;
      case 's':	/* SUBSTITUTE -- like sed	*/
      	fe->echar = *p ;
	if (fe->trim&(~(TRIMbl|TRIMdup|TRIMt)))
	    error("Can't have options and regular expression: ",  p-1) ;
	fe->trim |= TRIMsub;
	fe->cexp  = compile_expr(p);	/* On return from compile,
				pregexp -> end of Regular Expression  */
	fe->text = pregexp ;	/* Beginning of substitution string   */
	p = pregexp + strlen(pregexp) ;	/* Remainging text = argument */
	continue ;
      default:
      	isok = 0 ; p-- ;
    }

    if (*p == '+') {		/* Counter specification */
	if (fe->trim&TRIMado)
	    error("Can't have counter and addition: ",  p-1) ;
	if (ftype != FIELDc)
	    error("+Counter allowed only in -c mode:", string);
    	++p ;
    	fe->wcnt = 1 ;
    	if (isdigit(*p)) {
    	    fe->wcnt = atoi(p);
    	    while (isdigit(*p)) p++ ;
    	}
    	if (*p) switch(*(p++)) {
    	    case 'a': fe->trim |= TRIMcnt ;	/* NO BREAK */
    	    case 'A': fe->trim |= (2*TRIMcnt) ; break ;
    	    case 'x': fe->trim |= TRIMcnt ; 	/* NO BREAK */
    	    case 'X': fe->trim |= (4*TRIMcnt) ; break ;
    	    case 'o': fe->trim |= (6*TRIMcnt) ; break ;
    	    case 'd': fe->trim |= (TRIMcnt) ;   break ;
    	    default:  fprintf(stderr, "****acut: Bad char '%c' in %s\n",
    	    	p[-1], string) ;
	    error("Only allowed are: ", "doxa") ;
    	}
    	else fe->trim |= TRIMcnt;
    }

    if ((*p == '<') || (*p == '>')) { char *s; 
	/* Set min + max: char values in fe->text,
	 * numeric values in fe->cexp,
	 * offsets of min/max in fe->cexp[2] (bytes */
	if (fe->trim&(TRIMado|TRIMcnt))
	    error("Can't have max/minimisation with counter/addition: ", p);
	if (fe->trim&(TRIMmax|TRIMmin))
	    error("Can't have several min/max in a field: ", p);
	fe->trim |= TRIMnum|TRIMh|TRIMt;       /*|TRIMmax*/
	s = fe->text = strdup(p);
	fe->cexp = malloc(3*sizeof(double));   /* values + offsets */
	memset(fe->cexp, -1, 3*sizeof(double)); /* Put to NaN */
	while (*s) {
	    if      (*s == '<') i = 1;
	    else if (*s == '>') i = 0;
	    else i = -1;
	    if (i>=0) { 
		*(s++) = 0;
		val = atof(s);
		memcpy(fe->cexp+i*sizeof(double), &val, sizeof(double));
		fe->cexp[2*sizeof(double)+i] = (s-fe->text);
		fe->trim |= i ? TRIMmax : TRIMmin;
	    }
	    else s++;
	}
	p += (s-fe->text);
    }

    if (*p) {
	fprintf(stderr, "    %s\n    ", string);
	while(string < p) fputc(' ', stderr), string++;
	fprintf(stderr, "^\n");
	error("Invalid column definition", "");
    }
    	/* Install specs to all fields */
    for (fp = f1; fp <= fe; fp++) {
    	fp->width = fe->width;
    	fp->ndec  = fe->ndec ;
    	fp->text  = fe->text ;	/* V3.31 */
    	fp->trim |= (fe->trim & (~TRIMad));
    	if ((fp->width == 0) && (ftype == FIELDc) && (fp->col1 != MAXCOL))
    	    fp->width = fp->col1 - fp->col0;
	if (fe->trim & (TRIMbl|TRIMdup|TRIMcnT)) /* Keep previous record */
	    fp->text = Malloc(fp->width+1); /* V3.31 */
#ifdef DEBUG
	fprintf(stderr, "----Field %d/%d, w=%d, nd=%d, trim=%X\n",
    	    fp->col0, fp->col1, fp->width, fp->ndec, fp->trim);
#endif
    }
    return(fe) ;
}

/*==================================================================
 *		Execute the Transformation on Opened File
 *==================================================================*/
static int trc(in)
/*++++++++++++++++
.PURPOSE  Execute transformations on an opened file (-c option)
.RETURNS  0
.REMARKS  A numeric field is defined by pointers:

          |__________.|_|_|E|_|_|_____|:|:|
           ^                     ^     ^   ^
           b                     e     s   p
           <---(number)---------->
                      <----nd---->
                      <-----------(ndec)--->
           <--(expoform)--->

-----------------*/
  FILE *in;	/* IN: Opened file */
{
  FIELD *fe, *fe1, *pf;
  static char buf[BUFSIZ], bufa[BUFSIZ] ;
  static char fmt[30] ;
  static int unaligned, truncated, bounded;
  int  n, j, nb, nd, nbt, cnt, dif_prev=0, expoform, modif;
  char c, qual, *line, *b, *p, *e, *s, *eol, *a;
  char trailing_char;
  char **d0, **d1, **pd ;	/* Delimiters only */
  double val, *aval;
  int lineno=0;

#ifdef DEBUG
    printf("....Starting trc; there are %d fields\n", nf) ;
#endif
    fe = ff + nf; fe1 = fe - 1;		/* fe1 = last field */
    unaligned = truncated = bounded = 0;

    /* If fields defined by a delimiter exist, need the array of positions */
    if (found_fields&FIELDf) {
    	n = 1+maxf;	/* maxf delimiters, 1+maxf fields */
    	d0 = (char **)Malloc(n*sizeof(char *)), d1 = d0 + n;
    }
    else d0 = d1 = (char **)0 ;


    /*===============================Loop on input lines =============*/
    for (; ; putchar('\n')) {
  	line = getLine(in);
	if (!line) break;
	n = strlen(line) ;
  	if (n < 0) break;
	/* V4.31: headlines just transmitted */
	lineno++;
	if(lineno < headlines) continue;
	/* Empty Line = Separators -- just transmitted
	 * However, V3.92: option -e
	 * */
	if ((n == 0)  && (opte == 0)) 
	    continue;	
	if (comlen && (strncmp(line, comment, comlen) == 0)) {
	    printf("%s", line) ;
	    continue ;
	}
	eol = line + n ;
	/*--------Split the input line----------*/
	if (found_fields&FIELDf) {
	    pd = d0 ; *(pd++) = line-1;
	    for (p = line; p<eol; p++) {
	    if (*p != *delim) continue;
	  	*(pd++) = p;
	  	if (pd >= d1) break;
	    }
	    while (pd < d1) *(pd++) = p;	/* p is EOL */
	}

	/*====Loop on the arguments passed to the program ============*/
        for (pf=ff; pf<fe; pf++) {
#ifdef DEBUG
	    printf("<Partial Output #%d; n=%d", pf-ff,n);
	    printf("----Field %d/%d, w=%d, nd=%d, trim=%X, text=%s>\n",
    	    	pf->col0, pf->col1, pf->width, pf->ndec, pf->trim,
		pf->text ? pf->text : "(nil)");
	    fflush(stdout);
#endif
            if (pf->col0 < 0) { 	/* -i spec. */
            	if(pf->text) printf("%s", pf->text);
            	continue;
            }

	    j = pf->col0 ;
	  NextCol: 			/* This label only for -f ----*/
	    trailing_char = ' ';
	    if (pf->ftype&FIELDf) {	/* Field defined with  -f ----*/
		b = d0[j++] + 1 ;
		if (b > eol) b = eol ;
		p = (j <= maxf ? d0[j] : eol) ;
	    }
	    else {
		b = line + pf->col0 ;
		p = pf->col1 == MAXCOL ? eol : line + pf->col1 ;
		/* Trim the ending blanks if there is some transformation.
		 * not useful (V3.41)
		   --- but NOT in check blanks !
		*/
		if (pf->trim == 0) ;
		else if (pf->trim&(TRIMbl|TRIMdup|TRIMsub|TRIMtr)) ;
		else {
		    while (--p >= b) { if (*p != ' ') break; }
		    ++p;
		}
	    }

            cnt = 0 ;			/* Counter	*/
	    qual = 0;			/* Quality	*/
	    modif = 0;			/* Field was (not yet) modified */
            if (b >= eol) {		/* Beyond EOL   */
		/* Last column ignored, unless a full width is provided	*/
            	if ((pf == fe1) && (fe1->col1 == MAXCOL)) continue;
            	b = p = eol ;
	    }
	    if (p > eol) p = eol ;

	    /*----Compare record with preceding one ----*/
	    if (pf->trim & (TRIMbl|TRIMcnT))
	    	dif_prev = strncmp(b, pf->text, p-b ) ;
	    if (pf->trim & (TRIMbl)) { 	/* Blank if identical to previous */
		if (dif_prev == 0) {
		    cnt = pf->cnt + 1 ;
		    while (b < p) b++;
		}
		else {
		    strncpy(pf->text, b, p-b), *(pf->text+(p-b))=0;
		    cnt = 1 ;
	        }
	    }
	    if (pf->trim & TRIMdup) {	/* Replace blanks */
		if (isBlank(b, p-b)) {
		    dif_prev = 0 ;
		    cnt = pf->cnt + 1 ;
		    c = *p;
		    strcpy(b, pf->text);
		    if (c) *p = c;	/* Keep not EOL	  */
		    else p = b + strlen(b);
		}
		else {
		    dif_prev = 1 ;
		    strncpy(pf->text, b, p-b), *(pf->text+(p-b))=0;
	    	}
	    }
	    else if (pf->trim & TRIMcnT)    /* Keep current column */
	    	strncpy(pf->text, b, p-b), *(pf->text+(p-b))=0;

	    /*------ Unquote a string -------------------------*/
	    if (pf->trim & TRIMunq) {
		a = s = b;	/* a: input; s: output */
		while ((a<p) && (*a == ' ')) a++;
		while (a < p) {
		    if (*a == '"') { 	/* A quote found -- end of string ? */
			++a;		/* Skip the quote, "" will be " */
			if (a>=p) continue;
		    }
		    *(s++) = *(a++) ;
		}
		while (s < p) *(s++) = ' ';
	    }

	    /*------ Result Expression Substitution -----------*/
	    if (pf->trim & TRIMsub) {
		/* Mod. V4.14 */
	        if (pf->trim & TRIMt) while ((p > b) && (p[-1] == ' ')) p--;
		c = *p ; *p = 0 ;
	    	nb = regsub(pf->cexp, pf->text, pf->echar, b) ;
		*p = c ;
		/* nb is the number of chars written out.
		   Write an error message of too large
		*/
		s = p ;		/* Eveything written out */
		nb =  pf->width - nb ;	/* Blanks to add */
		if (nb < 0) {
		    if (pf->ftype&FIELDf) ; /* Don't worry about lengthy text */
		    else {
			c = *p ; *p = 0 ;
		        unaligned++ ;
			sprintf(buf, "Can't align col#%d", j) ;
			/* if (isatty(2)) */ warning(buf, b);
			*p = c ;
		    }
		    nb = 0 ;
		}
		modif |= TRIMsub;
		goto AddFinalBlanks ;
	    }

	    /* All following operations can't happen when substitute
	       is called... ................................... */

	    if (pf->trim&TRIMado) {		/* Add Value	*/
		for (s=b; *s == ' ' && s < p; s++) ;
		if (s < p) {	/* Non-empty value. Compute it, */
		    c = *p ; *p = 0 ;
		    cnt = atoi(s) + pf->cnt ;
		    if (issign(*s)) s++ ;
		    while (isdigit(*s)) s++ ;
		    sprintf(fmt, "%%%dd", (int)(s-b)) ;/* Keep same width */
		    sprintf(bufa, fmt, cnt) ;
		    nb = strlen(bufa);
		    bufa[sizeof(bufa)-1] = 0 ;
		    strncpy(bufa+nb, s, sizeof(bufa)-1-nb) ;
		    *p = c ;
		    b = bufa; nb = strlen(bufa) ;
		    p = e = s = bufa + nb ;
		    while ((*b == ' ') && (nb > pf->width)) b++, nb-- ;
		    if (nb > pf->width) {
		        unaligned++ ;
			sprintf(buf, "Can't align col#%d (with -a%+d)",
			    j, pf->cnt) ;
			/* if (isatty(2)) */ warning(buf, b);
		    }
		    modif |= TRIMado;
		}
	    }

	    /*------Trim the column ----------------------------*/
	    if (pf->trim & TRIMh) while ((*b == ' ') && (b < p)) b++;
	    if (pf->trim & TRIMt) while ((p > b) && (p[-1] == ' ')) p--;

	    /*-------- We now know how many fill blanks */
	    nb = pf->width - (p-b);	/* Count of fill blanks */
	    if (nb < 0) nb = 0;

	    /*-----Numeric Column: prefix, number, suffixes-----*/
	    expoform = 0;
	    if ((b<p) && (pf->trim&TRIMnum)) {
		/* For a sexagesimal number: keep the sign at leftmost */
		if (pf->trim&TRIMsex) {
		    nbt = 0;
		    while ((*b == ' ') && (b<p)) b++, nbt++;
		    b -= nbt;
		    if ((nbt>0) && issign(b[nbt]))
		      b[0] = b[nbt], b[nbt] = ' ';
		    if ((*b == ' ') && (pf->trim&TRIMsgn) && (b<p))
		      b[0] = '+';
		    if ((pf->trim&TRIMzf) && (b<p)) {	/* Zero fill */
			nbt = issign(*b) ? 1 : 0;
			while (b[nbt] == ' ') b[nbt++] = '0';
		    }
		}
		else {
	    	    if (issign(*b)) {
	    	        s = b ;
	    	        while((b<p) && (b[1] == ' ')) b++, nb++ ;
		        if (pf->trim&TRIMzf) while (nb>0) {
			    *(b--) = '0';
			    nb--;
		        }
	    	        else *b = *s ;
	    	    }
	    	    else for (nbt = prenum(b, p-b); --nbt >= 0; b++)
	    	        putchar(*b) ;
	    	    while ((b<p) && (*b== ' ')) nb++, b++ ;
		}
	    	s = p - postnum(b, p-b, pf->trim&TRIMsex) ; /* Suffix */
	    	for (e=s; (e>b) && (e[-1] == ' '); e--) nb++ ;
		while ((s<p) && (*s == ' ')) { s++; nb++; } /*V4.0*/
	    	if (pf->trim & TRIMsex) {	/* Sexagesimal    */
		    nd = nbt = sexa_dec(b, e-b);
		    if ((pf->trim&TRIMseX) && (nb>0)
		      && ((nd == -3)||(nd == -1))) { int x;
			/* Transform the fraction, e.g. 25.5 --> 25:30 
			 *  or, in no separator (sexa_sep=0) --> 2530
			 */
			e -= 2; 
			if (sexa_sep) { *e = sexa_sep; nb--; x = *++e; }
			else  x = e[1];
			x = (x-'0')*6;
			*(e++) = '0' + x/10;
			*(e++) = '0' + x%10;
			nbt++;
		    }
		    if ((nbt==0) && (pf->ndec>0)) {	/* Add decimal point */
			if (e[-1] == '.') ;		/* Already there */
			else *(e++) = '.', nb--;
		    }
		}
	    	else if (pf->trim & TRIMd) {	/* Decimal number */
	    	    nd = decimals(b, e-b) ;
		    if ((nd<0) && pf->dpoint) {
			c = dpoint; dpoint = pf->dpoint;
			nd = decimals(b, e-b) ;
			dpoint = c ;
		    }
		    /* Verify the e-numbers */
		    /* V4.01: Accept e.g. 5e6 */
		    if ((e-b)>=3) {
			for (a=e-1; (a>b)&&isdigit(*a); a--) ;
			if (a>b) {
			    if (issign(*a)) --a;
			    if (tolower(*a) == 'e')
				expoform = a - b;
			    /* b[expoform] = 'e' */
			}
		    }
		}
	    	else nd = 0 ;
		/* Assign quality */
		if (pf->trim&(TRIMaq|TRIMaQ)) {
		    qual = 'a' + (pf->ndec - nd);
		    if (qual < 'a') qual = 'a';
		    if (pf->trim&TRIMaQ) qual -= 'a' - 'A';
		}

		/* Bound the number */
		if (pf->trim&(TRIMmax|TRIMmin)) { int k;   /* V3.7 */
		    c = *p ; *p = 0;
		    val = atof(b);
		    *p = c;
		    aval = (double *)pf->cexp;
		    if      ((pf->trim&TRIMmin) && (val < aval[0]))
			 k = 0;
		    else if ((pf->trim&TRIMmax) && (val > aval[1]))
			 k = 1;
		    else k = -1;
		    if (k>=0) {
			bounded++;
			modif |= k ? TRIMmax : TRIMmin;
		        /* Change all parameters of the field to edit */
			b = pf->text + pf->cexp[2*sizeof(double)+k];
			p = e = s = b + strlen(b);
			nb = pf->width - (e-b);
			if (nb<0) nb = 0;
			if (pf->trim & TRIMh) while(*b== ' ') nb++, b++;
			if (pf->trim & TRIMd) nd = decimals(b, e-b); /* V4.21 */
		    }
		}

		/* Round the Number if necessary (not when was bounded)
		   For sexagesimal, round even when number
		   of decimals does not change
		   (for proper alignment)
		*/
		if (((modif&(TRIMmin|TRIMmax))==0) && 
		    (pf->trim&TRIMrnd) &&
		   ((pf->trim&TRIMsex) || (nd > pf->ndec) || expoform)) {
		    truncated++ ;
		    /* sprintf(buf, "Truncated   col#%d", j) ;
		       warning(buf, b);
		    */
		    c = *p ; *p = 0 ;
		    if (pf->trim & TRIMsex) {	/* Sexagesimal	*/
			sexa_round(b, buf, pf->width, pf->ndec);
			nd = pf->ndec;
		    }
		    else {			/* Decimal...	*/
			if (expoform) {
			    /* Actual number of decimals, taking into
			       account the exponent */
			    nd = expoform - 1 - atoi(b+expoform+1);
			}
			if (nd > pf->ndec) nd = pf->ndec;
			val = atof(b) ;
			if (val>0)      val += rounding[nd];
			else if (val<0) val -= rounding[nd];
			/* In case of overflow, leave the original value */
			if (edit_dec(buf, val, pf->width, nd) < 0) {
		    	    sprintf(buf, "Can't round col#%d", j) ;
			    warning(buf, b);
			    unaligned++;
			    strcpy(buf, b);
			}
			else buf[pf->width] = 0;
		    }
		    *p = c ;
		    /*fprintf(stderr, "%....fmt='%s', buf='%s'\n", fmt, buf);*/
		    /* Change all parameters of the field to edit */
		    b = buf ; p = e = s = b + pf->width ;
		    nb = 0;
		    if (pf->trim & TRIMh) while(*b== ' ') nb++, b++;
		}

		/* Recompute number of blanks */
		if (pf->trim & TRIMsex) {
		    nbt = nb;
		    nd = 0;	/* Don't worry any more about decimal point */
		}
		else nbt = pf->ndec - nd - (p-s) ;
	    	if ((nbt < 0) || (nb < nbt)) {
		    if (nbt<0) nbt = 0;
	    	    c = *p ; *p = 0 ;
		    unaligned++ ;
		    sprintf(buf, "Can't align col#%d", j) ;
		    /* if (isatty(2)) */ warning(buf, b);
		    *p = c ;
		}
		/*----Signed number filled with zeroes----------*/
		if (pf->trim & TRIMzf) {
		    if (issign(*b)) { putchar(*b); b++; }
		    else if (pf->trim & TRIMsgn) { putchar('+'); nb--; }
	    	    while (nb > nbt) { putchar('0'); nb--; }
		}
	    }

	    /*--------Non-Numeric: compute leading&trailing-----*/
	    else {
	    	nd = 0;
	    	e = s = p ;
		if (pf->trim & TRIMlad)	/* Left-Adjusment, V3.94 */
		    nbt = nb;
		else if (pf->trim & TRIMt) 
		    nbt = 0;
		else nbt = nb;
		if (pf->ftype&FIELDa)	/* V3.93: No blanks in -a mode */
		    nbt = nb = 0;
	    }
	    /*--------Write out leading blanks------------------*/
	    c = (pf->trim & TRIMzf) && (b<e) ? '0' : ' ';    /* V4.11, 4.12 */
	    while (nb > nbt) { putchar(c); nb--; }

	    /*--------Write out value --------------------------*/
	    if (b < e) {	/* V3.41: Only when non-null value */
                if (pf->trim & TRIMtr) {      /* TRANSLATE Text */
		    while(b<e) {
            	        putchar(pf->text[*b & 0xff]);
            	        b++;
		    }
		    trailing_char = pf->text[' '];
	        }
                else while(b<e) { 
		    putchar(*b); b++ ; 
		}
                if ((nd < 0) && (nb > 0))
		    { putchar('.'); nb--; }
	    }

	  AddFinalBlanks:
            if ((pf != fe1) || (s!=p) || (pf->ftype&FIELDf))
		while (--nb >= 0) putchar(trailing_char);
	    while (s<p) { putchar(*s); s++; }	/* Add Trailing	*/

	    /*----Add the Quality, if any-----------------------*/
	    if (pf->trim&(TRIMaq|TRIMaQ)) {
		if (!qual) qual = ' ';
		putchar(qual);
	    }

            /*----Add the counter, if any-----------------------*/
            if (pf->trim&TRIMcnT) {
            	if (!cnt)  {	 	/* Compute the Counter 	*/
		    if (dif_prev) {	/* Compare with next	*/
			if (pf->ftype&FIELDf) cnt = 1 ;
			else if (!next) ;	/* Last record  */
			else {
			    s = next+pf->col0 ;	/* Next Record  */
			    if (pf->trim&TRIMdup) cnt = isBlank(s, pf->width) ;
			    else if (strncmp(pf->text, s, pf->width));
			    else cnt = 1 ;
			}
		    }
		    else cnt = pf->cnt + 1 ;
		}
            	pf->cnt = cnt ;
            	if (edit_counter(pf->trim&TRIMcnT, pf->wcnt, cnt) > pf->wcnt) {
		    unaligned++ ;
		    sprintf(buf, "Can't align col#%d(counter)", j) ;
		    /* if (isatty(2)) */ warning(buf, b);
		}
	    }

            /*----In -a option, add the delimiter... */
	    if (pf->ftype&FIELDa) {	/* V3.93 */
		if (pf<fe) printf("%s", delim);
	    }

            /*----In -f option, do the same on next column -----*/
	    if (pf->ftype&FIELDf) {
		if (j > maxf) continue ;
		if ((j < pf->col1) && (b < eol)) {
		    if (pf->trim&TRIMplus) ;	/* V4.2 */
		    else printf("%s", delim);
		    goto NextCol ;
		}
	    	if (pf->trim & TRIMad) printf("%s", delim);
	    }
	}
    }
    if (d0) free(d0) ;

    /* Write out the number of problems */
    if (truncated) fprintf(stderr,
        "++++Number of rounded/truncated values: %d\n", truncated) ;
    if (bounded) fprintf(stderr,
        "++++Number of bounded values: %d\n", bounded) ;
    if (unaligned) fprintf(stderr,
        "++++Count of misaligned values: %d\n", unaligned) ;
    return(0);
}


/*==================================================================
		Program
 *==================================================================*/
int main (int argc, char **argv)
{
  FILE *in;
  FIELD *pf;
  int   files, i, n;
  char  *p, *t1=(char *)0, *t2=(char *)0;
  char  s0[256], s1[256];

	/* Check the Arguments */

    files = 0;
    while (-- argc > 0) {
	p = *++argv;
	if (*p == '-') switch(*++p) {
	    case 'h':  
		if (strncmp(p, "head", 4) == 0) {
		    headlines = 1;
		    if(p = strchr(p, '='))
			headlines = atoi(p+1);
		    continue;
		}
		help();
		exit(0);
	    case 'v':
		if (strncmp(*argv, "-ver", 4) == 0) {
		     printf("acut (CDS) Version %s\n", VERSION);
		     exit(0);
		}
		goto case_default;
	    case 'a':
		++p;
		fields(p, FIELDa);
		continue;
	    case 'c':
		++p ;
		/* Just -c means: just copy text between the previous
		   column and the next definition.
		   In this case, just set the tofill flag, the appropriate
		   action will be done in the fields routine
		*/
		if ((!*p) || ((p[1]==0) && ((*p=='.')||(*p=='=')))) {
		    tofill = 1 ;
		    continue ;
		}
		fields(p, FIELDc);
		continue;
	    case 'e':
		opte = 1;
		continue;
	    case 'f':
		++p;
		/* Just -f or -f. or -f= identical to -c */
		if ((!*p) || ((p[1]==0) && ((*p=='.')||(*p=='=')))) {
		    tofill = 1 ;
		    continue ;
		}
		fields(p, FIELDf);
		continue;
	    case 'C':		/* Comment */
	        comment = p+1 ;
		comlen = strlen(comment) ;
		continue ;
	    case 'F':		/* Accept -d / -F */
	    case 'd':
	    	*delim = thebyte(p+1);
		if (*delim == 0) *delim = '\t';
		continue;
	    case 'b':		/* Add blanks e.g. -b16 = add 16 blanks */
		pf = fields("", FIELDc); nf++;
	    	pf->col0 = pf->col1 = -1;
		if ((i = atoi(p+1)) == 0) pf->text = delim;
		else {
		    pf->text = malloc(i+1);
		    memset(pf->text, ' ', i); 
		    pf->text[i] = 0;
		}
		continue;
	    case 'i':		/* Insert a string */
	    	pf = fields("", FIELDc); nf++;
	    	pf->col0 = pf->col1 = -1;
	    	pf->text = ++p;
	    	if (!*pf->text) 	/* Default Insert */
	    	    pf->text = delim ;
		continue;
	    case 'r':		/* Add some repetition: -r5 ' ' == -b5 */
	    	if (argc < 1) error("option -r requires a string; ", p-1);
		n = atoi(p+1); if (n<1) n=1; i = strlen(argv[1]);
		pf = fields("", FIELDc); nf++;
	    	pf->col0 = pf->col1 = -1;
		pf->text = malloc((n*i)+1);
		for (p=pf->text; --n>=0; p += i) strcpy(p, argv[1]);
		*p = 0; argv++; argc--;
		continue;
	    case 'T':		/* Translate */
	    case 't':		/* Translate */
		trt = Malloc(256);
  	    	for (i=0; i < 256; i++) trt[i] = i;
		switch(p[1]) {
		  case 'l': case 'L': case 'a':	/* Convert to lowercase */
		    t1 = "\\A", t2 = "\\a";
		    break;
		  case 'u': case 'U': case 'A':	/* Convert to UPPERcase */
		    t1 = "\\a"; t2 = "\\A";
		    /* NO BREAK */
		  case 0:
	    	    if (argc < 2) error("-T requires TWO strings: ", p-1);
		    t1 = argv[1];
		    t2 = argv[2];
		    argv += 2; argc -= 2;
		    break;
		  default:
		    error("Invalid option: ", p-1);
		}
		n = getstring(t1, s0, sizeof(s0));
		i = getstring(t2, s1, sizeof(s1));
		if (i != n) error("-T strings NOT of same length", "");
		for (i=0; i<n; i++) trt[s0[i]&0xff] = s1[i];
		continue;
	    case_default:
	    default:
		error("Unknown option: ", p-1);
	}
	else {	/* A File */
	    if (tofill && (uppercut > 0)) fields("-", FIELDc);
	    infile = p;		/* Save file name */
	    in = FOPEN(p);
	    if (!in) { perror(p); exit(1); }
	    files++; lineno=0;
	    trc(in);
	    FCLOSE(in);
	}
    }

	/* Execute on stdin if necessary */

    if (files == 0) {
	if (tofill && (uppercut > 0)) fields("-", FIELDc);
	trc(stdin);
    }

    exit(0);
}
