/*	SC	A Spreadsheet Calculator
 *		Expression interpreter and assorted support routines.
 *
 *		original by James Gosling, September 1982
 *		modified by Mark Weiser and Bruce Israel, 
 *			University of Maryland
 *
 *              More mods Robert Bond, 12/86
 *		More mods by Alan Silverstein, 3-4/88, see list of changes.
 *		$Revision: 7.16 $
 */

#include <sys/types.h>
#ifdef aiws
#undef _C_func			/* Fixes for undefined symbols on AIX */
#endif

#ifdef IEEE_MATH
#include <ieeefp.h>
#endif /* IEEE_MATH */

#include <math.h>
#include <signal.h>
#include <setjmp.h>
#include <ctype.h>
#include <errno.h>

#ifdef BSD42
#include <strings.h>
#include <sys/time.h>
#ifndef strchr
#define strchr index
#endif
#else
#include <time.h>
#ifndef SYSIII
#include <string.h>
#endif
#endif

#include <stdlib.h>
#include <curses.h>
#include "sc.h"

#ifndef MSDOS
#include <unistd.h>
#endif

#ifdef REGCOMP
#include <regex.h>
#endif
#ifdef RE_COMP
extern char *re_comp(char *s);
extern char *re_exec(char *s);
#endif
#ifdef REGCMP
char *regcmp();
char *regex();
#endif

#ifdef SIGVOID
    void doquit();
#else
    int doquit();
#endif

/* Use this structure to save the last 'g' command */
struct go_save gs;

/* g_type can be: */
#define G_NONE 0	/* Starting value - must be 0 */
#define G_NUM 1
#define G_STR 2
#define G_NSTR 3
#define G_XSTR 4
#define G_CELL 5

#define ISVALID(r,c)	((r)>=0 && (r)<maxrows && (c)>=0 && (c)<maxcols)

jmp_buf fpe_save;
int	exprerr;	/* Set by eval() and seval() if expression errors */
double  prescale = 1.0;	/* Prescale for constants in let() */
int	extfunc  = 0;	/* Enable/disable external functions */
int     loading = 0;	/* Set when readfile() is active */
int	gmyrow, gmycol;	/* globals used to implement @myrow, @mycol cmds */
int	rowoffset = 0, coloffset = 0;	/* row & col offsets for range
					   functions */

extern bool decimal;	/* Set if there was a decimal point in the number */

/* a linked list of free [struct enodes]'s, uses .e.o.left as the pointer */
struct enode *freeenodes = NULL;

double	dolookup(struct enode * val, int minr, int minc, int maxr,
	int maxc, int offr, int offc);
double	fn1_eval(double (*fn)(), double arg);
double	fn2_eval(double (*fn)(), double arg1, double arg2);
int	RealEvalAll();
int	constant(register struct enode *e);
void	RealEvalOne(register struct ent *p, int i, int j, int *chgct);
void	copydbuf(int deltar, int deltac);
void	decompile(register struct enode *e, int	priority);
void	index_arg(char *s, struct enode *e);
void	list_arg(char *s, struct enode *e);
void	one_arg(char *s, struct enode *e);
void	range_arg(char *s, struct enode *e);
void	three_arg(char *s, struct enode *e);
void	two_arg(char *s, struct enode *e);
void	two_arg_index(char *s, struct enode *e);

double	rint(double d);
int	cellerror = CELLOK;	/* is there an error in this cell */

#ifndef M_PI
#define M_PI (double)3.14159265358979323846
#endif
#define dtr(x) ((x)*(M_PI/(double)180.0))
#define rtd(x) ((x)*(180.0/(double)M_PI))

double
finfunc(int fun, double v1, double v2, double v3)
{
 	double answer,p;
 
 	p = fn2_eval(pow, 1 + v2, v3);
 
 	switch (fun) {
 	    case PV:
		if (v2)
		    answer = v1 * (1 - 1/p) / v2;
		else {
		    cellerror = CELLERROR;
		    answer = (double)0;
		}
 		break;
 	    case FV:
		if (v2)
		    answer = v1 * (p - 1) / v2;
		else {
		    cellerror = CELLERROR;
		    answer = (double)0;
		}
 		break;
 	    case PMT:
		/* CHECK IF ~= 1 - 1/1 */
		if (p && p != (double)1)
		    answer = v1 * v2 / (1 - 1/p);
		else {
		    cellerror = CELLERROR;
		    answer = (double)0;
		}
		
 		break;
	    default:
		error("Unknown function in finfunc");
		cellerror = CELLERROR;
		return ((double)0);
	}
	return (answer);
}

char *
dostindex(int minr, int minc, int maxr, int maxc, struct enode *val)
{
    int r, c;
    register struct ent *p;
    char *pr;

    p = (struct ent *)0;
    if (minr == maxr) {			/* look along the row */
	r = minr;
	c = minc + (int) eval(val) - 1;
    } else if (minc == maxc) {		/* look down the column */
	r = minr + (int) eval(val) - 1;
	c = minc;
    } else {
	r = minr + (int) eval(val->e.o.left) - 1;
	c = minc + (int) eval(val->e.o.right) - 1;
    }
    if (c <= maxc && c >=minc && r <= maxr && r >=minr)
	p = *ATBL(tbl, r, c);

    if (p && p->label) {
	pr = scxmalloc((size_t)(strlen(p->label)+1));
	(void) strcpy(pr, p->label);
	if (p->cellerror)
	    cellerror = CELLINVALID;
	return (pr);
    } else
	return ((char *)0);
}

double
doindex(int minr, int minc, int maxr, int maxc, struct enode *val)
{
    int r, c;
    register struct ent *p;

    if (val->op == ',') {		/* index by both row and column */
	r = minr + (int) eval(val->e.o.left) - 1;
	c = minc + (int) eval(val->e.o.right) - 1;
    } else if (minr == maxr) {		/* look along the row */
	r = minr;
	c = minc + (int) eval(val) - 1;
    } else if (minc == maxc) {		/* look down the column */
	r = minr + (int) eval(val) - 1;
	c = minc;
    } else {
	error("Improper indexing operation");
	return (double) 0;
    }

    if (c <= maxc && c >=minc && r <= maxr && r >=minr &&
	    (p = *ATBL(tbl, r, c)) && p->flags&is_valid) {
	if (p->cellerror)
	    cellerror = CELLINVALID;
	return p->v;
    } else
	return (double) 0;
}

double
dolookup(struct enode * val, int minr, int minc, int maxr, int maxc, int offset, int vflag)
{
    double v, ret = (double)0;
    int r, c;
    register struct ent *p = (struct ent *)0;
    int incr, incc, fndr, fndc;
    char *s;

    incr = vflag; incc = 1 - vflag;
    if (etype(val) == NUM) {
	cellerror = CELLOK;
	v = eval(val);
	for (r = minr, c = minc; r <= maxr && c <= maxc; r+=incr, c+=incc) {
	    if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid) {
		if (p->v <= v) {
		    fndr = incc ? (minr + offset) : r;
		    fndc = incr ? (minc + offset) : c;
		    if (ISVALID(fndr, fndc))
			p = *ATBL(tbl, fndr, fndc);
		    else {
			error(" range specified to @[hv]lookup");
			cellerror = CELLERROR;
		    }
		    if (p && p->flags&is_valid) {
		    	if (p->cellerror)
			    cellerror = CELLINVALID;
			ret = p->v;
		    }
		} else break;
	    }
	}
    } else {
	cellerror = CELLOK;
	s = seval(val);
	for (r = minr, c = minc; r <= maxr && c <= maxc; r+=incr, c+=incc) {
	    if ((p = *ATBL(tbl, r, c)) && p->label) {
		if (strcmp(p->label,s) == 0) {
		    fndr = incc ? (minr + offset) : r;
		    fndc = incr ? (minc + offset) : c;
		    if (ISVALID(fndr,fndc)) {
		    	p = *ATBL(tbl, fndr, fndc);
			if (p->cellerror)
				cellerror = CELLINVALID;
		    } else {
			error(" range specified to @[hv]lookup");
			cellerror = CELLERROR;
		    }
		    break;
		}
	    }
	}
	if (p && p->flags&is_valid)
	    ret = p->v;
	scxfree(s);
    }
    return ret;
}

double
docount(int minr, int minc, int maxr, int maxc, struct enode *e)
{
    int v;
    int r, c;
    int cellerr = CELLOK;
    register struct ent *p;

    v = 0;
    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++) {
	    if (e) {
		rowoffset = r - minr;
		coloffset = c - minc;
	    }
	    if (!e || eval(e))
		if ((p = *ATBL(tbl, r, c)) && p->flags & is_valid) {
		    if (p->cellerror)
			cellerr = CELLINVALID;
		    v++;
		}
	}
    cellerror = cellerr;
    rowoffset = coloffset = 0;
    return v;
}

double
dosum(int minr, int minc, int maxr, int maxc, struct enode *e)
{
    double v;
    int r, c;
    int cellerr = CELLOK;
    register struct ent *p;

    v = (double)0;
    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++) {
	    if (e) {
		rowoffset = r - minr;
		coloffset = c - minc;
	    }
	    if (!e || eval(e))
		if ((p = *ATBL(tbl, r, c)) && p->flags & is_valid) {
		    if (p->cellerror)
			cellerr = CELLINVALID;
		    v += p->v;
		}
	}
    cellerror = cellerr;
    rowoffset = coloffset = 0;
    return v;
}

double
doprod(int minr, int minc, int maxr, int maxc, struct enode *e)
{
    double v;
    int r, c;
    int cellerr = CELLOK;
    register struct ent *p;

    v = 1;
    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++) {
	    if (e) {
		rowoffset = r - minr;
		coloffset = c - minc;
	    }
	    if (!e || eval(e))
		if ((p = *ATBL(tbl, r, c)) && p->flags & is_valid) {
		    if (p->cellerror)
			cellerr = CELLINVALID;
		    v *= p->v;
		}
	}
    cellerror = cellerr;
    rowoffset = coloffset = 0;
    return v;
}

double
doavg(int minr, int minc, int maxr, int maxc, struct enode *e)
{
    double v;
    int r, c;
    int count;
    int cellerr = CELLOK;
    register struct ent *p;

    v = (double)0;
    count = 0;
    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++) {
	    if (e) {
		rowoffset = r - minr;
		coloffset = c - minc;
	    }
	    if (!e || eval(e))
		if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid) {
		    if (p->cellerror)
			cellerr = CELLINVALID;

		    v += p->v;
		    count++;
		}
	}
    cellerror = cellerr;
    rowoffset = coloffset = 0;

    if (count == 0) 
	return ((double)0);

    return (v / (double)count);
}

double
dostddev(int minr, int minc, int maxr, int maxc, struct enode *e)
{
    double lp, rp, v, nd;
    int r, c;
    int n;
    int cellerr = CELLOK;
    register struct ent *p;

    n = 0;
    lp = 0;
    rp = 0;
    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++) {
	    if (e) {
		rowoffset = r - minr;
		coloffset = c - minc;
	    }
	    if (!e || eval(e))
		if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid) {
		    if (p->cellerror)
			cellerr = CELLINVALID;

		    v = p->v;
		    lp += v*v;
		    rp += v;
		    n++;
		}
	}
    cellerror = cellerr;
    rowoffset = coloffset = 0;

    if ((n == 0) || (n == 1)) 
	return ((double)0);
    nd = (double)n;
    return (sqrt((nd*lp-rp*rp)/(nd*(nd-1))));
}

double
domax(int minr, int minc, int maxr, int maxc, struct enode *e)
{
    double v = (double)0;
    int r, c;
    int count;
    int cellerr = CELLOK;
    register struct ent *p;

    count = 0;
    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++) {
	    if (e) {
		rowoffset = r - minr;
		coloffset = c - minc;
	    }
	    if (!e || eval(e))
		if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid) {
		    if (p->cellerror)
			cellerr = CELLINVALID;

		    if (!count) {
			v = p->v;
			count++;
		    } else if (p->v > v)
			v = p->v;
		}
	}
    cellerror = cellerr;
    rowoffset = coloffset = 0;

    if (count == 0) 
	return ((double)0);

    return (v);
}

double
domin(int minr, int minc, int maxr, int maxc, struct enode *e)
{
    double v = (double)0;
    int r, c;
    int count;
    int cellerr = CELLOK;
    register struct ent *p;

    count = 0;
    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++) {
	    if (e) {
		rowoffset = r - minr;
		coloffset = c - minc;
	    }
	    if (!e || eval(e))
		if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid) {
		    if (p->cellerror)
			cellerr = CELLINVALID;

		    if (!count) {
			v = p->v;
			count++;
		    } else if (p->v < v)
			v = p->v;
		}
	}
    cellerror = cellerr;
    rowoffset = coloffset = 0;

    if (count == 0) 
	return ((double)0);

    return (v);
}

int mdays[12]={ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

double
dodts(int e1, int e2, int e3)
{
    int		yr, mo, day;
    time_t	secs;
    struct tm	t; 

    if (e2 > 12 || e3 > 31) {
	mo  = e1;
	day = e2;
	yr  = e3;
    } else {
	yr  = e1;
	mo  = e2;
	day = e3;
    }
    mdays[1] = 28 + (yr%4 == 0) - (yr%100 == 0) + (yr%400 == 0);

    t.tm_hour = t.tm_min = t.tm_sec = 0;
    t.tm_mon = --mo;
    t.tm_mday = day;
    t.tm_year = yr -= 1900;
    t.tm_isdst = -1;

    if (mo < 0 || mo > 11 || day < 1 || day > mdays[mo] ||
               (secs = mktime(&t)) == -1) {
        error("@dts: invalid argument or date out of range");
        cellerror = CELLERROR;
        return (0.0);
    }

    return ((double)secs);
}

double
dotts(int hr, int min, int sec)
{
    if (hr < 0 || hr > 23 || min < 0 || min > 59 || sec < 0 || sec > 59) {
	error ("@tts: Invalid argument");
	cellerror = CELLERROR;
	return ((double) 0);
    }
    return ((double)(sec+min*60+hr*3600));
}

double
dotime(int which, double when)
{
	static time_t t_cache;
	static struct tm tm_cache;
	struct tm *tp;
	time_t tloc;

	if (which == NOW) 
	    return (double)time(NULL);

	tloc = (time_t)when;

	if (tloc != t_cache) {
	    tp = localtime(&tloc);
	    tm_cache = *tp;
	    tm_cache.tm_mon += 1;
	    tm_cache.tm_year += 1900;
	    t_cache = tloc;
	}

	switch (which) {
	    case HOUR:		return ((double)(tm_cache.tm_hour));
	    case MINUTE:	return ((double)(tm_cache.tm_min));
	    case SECOND:	return ((double)(tm_cache.tm_sec));
	    case MONTH:		return ((double)(tm_cache.tm_mon));
	    case DAY:		return ((double)(tm_cache.tm_mday));
	    case YEAR:		return ((double)(tm_cache.tm_year));
	}
	/* Safety net */
	cellerror = CELLERROR;
	return ((double)0);
}

double
doston(char *s)
{
    double v;

    if (!s)
	return ((double)0);

    v = strtod(s, NULL);
    scxfree(s);
    return(v);
}

double
doeqs(char *s1, char *s2)
{
    double v;

    if (!s1 && !s2)
	return ((double)1.0);

    if (!s1 || !s2)
	v = 0.0;
    else if (strcmp(s1, s2) == 0)
	v = 1.0;
    else
	v = 0.0;

    if (s1)
    	scxfree(s1);

    if (s2)
    	scxfree(s2);

    return(v);
}


/*
 * Given a string representing a column name and a value which is a row
 * number, return a pointer to the selected cell's entry, if any, else NULL.
 * Use only the integer part of the column number.  Always free the string.
 */

struct ent *
getent(char *colstr, double rowdoub)
{
    int collen;		/* length of string */
    int row, col;	/* integer values   */
    struct ent *p = (struct ent *)0;	/* selected entry   */

    if (!colstr) {
    	cellerror = CELLERROR;
	return ((struct ent *)0);
    }

    if (((row = (int) floor(rowdoub)) >= 0)
	    && (row < maxrows)				/* in range */
	    && ((collen = strlen (colstr)) <= 2)	/* not too long */
	    && ((col = atocol (colstr, collen)) >= 0)
	    && (col < maxcols)) {			/* in range */
	p = *ATBL(tbl, row, col);
	if ((p != NULL) && p->cellerror)
	    cellerror = CELLINVALID;
    }
    scxfree(colstr);
    return (p);
}


/*
 * Given a string representing a column name and a value which is a column
 * number, return the selected cell's numeric value, if any.
 */

double
donval(char *colstr, double rowdoub)
{
    struct ent *ep;

    return (((ep = getent(colstr, rowdoub)) && ((ep->flags) & is_valid)) ?
	    (ep->v) : (double)0);
}


/*
 *	The list routines (e.g. dolmax) are called with an LMAX enode.
 *	The left pointer is a chain of ELIST nodes, the right pointer
 *	is a value.
 */
double
dolmax(struct enode *ep)
{
    register int count = 0;
    register double maxval = 0; /* Assignment to shut up lint */
    register struct enode *p;
    register double v;

    cellerror = CELLOK;
    for (p = ep; p; p = p->e.o.left) {
	v = eval(p->e.o.right);
	if (!count || v > maxval)
	    maxval = v; count++;
    }
    if (count) return maxval;
    else return (double)0;
}

double
dolmin(struct enode *ep)
{
    register int count = 0;
    register double minval = 0; /* Assignment to shut up lint */
    register struct enode *p;
    register double v;

    cellerror = CELLOK;
    for (p = ep; p; p = p->e.o.left) {
	v = eval(p->e.o.right);
	if (!count || v < minval)
	    minval = v; count++;
    }
    if (count) return minval;
    else return (double)0;
}

double 
eval(register struct enode *e)
{
    if (e == (struct enode *)0) {
    	cellerror = CELLINVALID;
	return (double)0;
    }
    switch (e->op) {
	case '+':	return (eval(e->e.o.left) + eval(e->e.o.right));
	case '-':	{
			double l, r;
			l = eval(e->e.o.left);
			r = eval(e->e.o.right);
			return l - r;
			}
	case '*':	return (eval(e->e.o.left) * eval(e->e.o.right));
	case '/':	{
			double num, denom;
			num = eval(e->e.o.left);
			denom = eval(e->e.o.right);
			if (cellerror) {
			    cellerror = CELLINVALID;
			    return ((double) 0);
			} else
			if (denom)
			    return (num/denom);
			else {
			    cellerror = CELLERROR;
			    return ((double) 0);
			}
	}
	case '%':	{
			double num, denom;
			num = floor(eval(e->e.o.left));
			denom = floor(eval(e->e.o.right));
			if (denom)
			    return (num - floor(num/denom)*denom);
			else {
			    cellerror = CELLERROR;
			    return ((double) 0);
			}
	}
	case '^':	return (fn2_eval(pow,eval(e->e.o.left),eval(e->e.o.right)));
	case '<':	return (eval(e->e.o.left) < eval(e->e.o.right));
	case '=':	{
			double l, r;
			l = eval(e->e.o.left);
			r = eval(e->e.o.right);
			return (l == r);
			}
	case '>':	return (eval(e->e.o.left) > eval(e->e.o.right));
	case '&':	return (eval(e->e.o.left) && eval(e->e.o.right));
	case '|':	return (eval(e->e.o.left) || eval(e->e.o.right));
	case IF:
	case '?':	return eval(e->e.o.left) ? eval(e->e.o.right->e.o.left)
						: eval(e->e.o.right->e.o.right);
	case 'm':	return (-eval(e->e.o.left));
	case 'f':	{
			int rtmp = rowoffset;
			int ctmp = coloffset;
			double ret;
			rowoffset = coloffset = 0;
			ret = eval(e->e.o.left);
			rowoffset = rtmp;
			coloffset = ctmp;
			return (ret);
			}
	case 'F':	return (eval(e->e.o.left));
	case '!':	return (eval(e->e.o.left) == 0.0);
	case ';':	return (((int)eval(e->e.o.left) & 7) +
				(((int)eval(e->e.o.right) & 7) << 3));
	case O_CONST:	if (!finite(e->e.k)) {
			    e->op = ERR_;
			    e->e.k = (double) 0;
			    cellerror = CELLERROR;
			}
			return (e->e.k);
	case O_VAR:	{
			struct ent *vp = e->e.v.vp;
			int row, col;
			if (vp && (rowoffset || coloffset)) {
			    row = e->e.v.vf & FIX_ROW ? vp->row
						      : vp->row + rowoffset;
			    col = e->e.v.vf & FIX_COL ? vp->col
						      : vp->col + coloffset;
			    checkbounds(&row, &col);
			    vp = *ATBL(tbl, row, col);
			}
			if (!vp || vp->flags & is_deleted) {
			    cellerror = CELLERROR;
			    return (double) 0;
			}
			if (vp->cellerror)
			    cellerror = CELLINVALID;
			return (vp->v);
			}
	case SUM:
 	case PROD:
 	case AVG:
 	case COUNT:
 	case STDDEV:
	case MAX:
	case MIN:
	case INDEX:
	case LOOKUP:
	case HLOOKUP:
	case VLOOKUP: {
	    int r, c;
	    int maxr, maxc;
	    int minr, minc;
	    maxr = e->e.o.left->e.r.right.vp->row;
	    maxc = e->e.o.left->e.r.right.vp->col;
	    minr = e->e.o.left->e.r.left.vp->row;
	    minc = e->e.o.left->e.r.left.vp->col;
	    if (minr>maxr) r = maxr, maxr = minr, minr = r;
	    if (minc>maxc) c = maxc, maxc = minc, minc = c;
		switch (e->op) {
		    case LOOKUP:
			return dolookup(e->e.o.right, minr, minc, maxr, maxc,
				1, minc==maxc);
		    case HLOOKUP:
			return dolookup(e->e.o.right->e.o.left, minr,minc,maxr,maxc,
			    (int) eval(e->e.o.right->e.o.right), 0);
		    case VLOOKUP:
			return dolookup(e->e.o.right->e.o.left, minr,minc,maxr,maxc,
			    (int) eval(e->e.o.right->e.o.right), 1);
		    case INDEX:
			return doindex(minr, minc, maxr, maxc, e->e.o.right);
	            case SUM:
			return dosum(minr, minc, maxr, maxc, e->e.o.right);
	            case PROD:
			return doprod(minr, minc, maxr, maxc, e->e.o.right);
	            case AVG:
			return doavg(minr, minc, maxr, maxc, e->e.o.right);
	            case COUNT:
			return docount(minr, minc, maxr, maxc, e->e.o.right);
	            case STDDEV:
			return dostddev(minr, minc, maxr, maxc, e->e.o.right);
	            case MAX:
			return domax(minr, minc, maxr, maxc, e->e.o.right);
	            case MIN:
			return domin(minr, minc, maxr, maxc, e->e.o.right);
		}
	}
 	case REDUCE | 'R':
 	case REDUCE | 'C':
	    {	int r, c;
		int maxr, maxc;
		int minr, minc;
		maxr = e->e.r.right.vp->row;
		maxc = e->e.r.right.vp->col;
		minr = e->e.r.left.vp->row;
		minc = e->e.r.left.vp->col;
		if (minr>maxr) r = maxr, maxr = minr, minr = r;
		if (minc>maxc) c = maxc, maxc = minc, minc = c;
	        switch (e->op) {
 	            case REDUCE | 'R': return (maxr - minr + 1);
 	            case REDUCE | 'C': return (maxc - minc + 1);
		}
	    }
	case ABS:	 return (fn1_eval( fabs, eval(e->e.o.left)));
	case ACOS:	 return (fn1_eval( acos, eval(e->e.o.left)));
	case ASIN:	 return (fn1_eval( asin, eval(e->e.o.left)));
	case ATAN:	 return (fn1_eval( atan, eval(e->e.o.left)));
	case ATAN2:	 return (fn2_eval( atan2, eval(e->e.o.left), eval(e->e.o.right)));
	case CEIL:	 return (fn1_eval( ceil, eval(e->e.o.left)));
	case COS:	 return (fn1_eval( cos, eval(e->e.o.left)));
	case EXP:	 return (fn1_eval( exp, eval(e->e.o.left)));
	case FABS:	 return (fn1_eval( fabs, eval(e->e.o.left)));
	case FLOOR:	 return (fn1_eval( floor, eval(e->e.o.left)));
	case HYPOT:	 return (fn2_eval( hypot, eval(e->e.o.left), eval(e->e.o.right)));
	case LOG:	 return (fn1_eval( log, eval(e->e.o.left)));
	case LOG10:	 return (fn1_eval( log10, eval(e->e.o.left)));
	case POW:	 return (fn2_eval( pow, eval(e->e.o.left), eval(e->e.o.right)));
	case SIN:	 return (fn1_eval( sin, eval(e->e.o.left)));
	case SQRT:	 return (fn1_eval( sqrt, eval(e->e.o.left)));
	case TAN:	 return (fn1_eval( tan, eval(e->e.o.left)));
	case DTR:	 return (dtr(eval(e->e.o.left)));
	case RTD:	 return (rtd(eval(e->e.o.left)));
	case RND:
	    if (rndtoeven)
		return rint(eval(e->e.o.left));
	    else {
		double temp = eval(e->e.o.left);
		return (temp - floor(temp) < 0.5 ? floor(temp) : ceil(temp));
	    }
 	case ROUND:
	    {	int precision = (int) eval(e->e.o.right);
		double scale = 1;
		if (0 < precision)
		    do scale *= 10; while (0 < --precision);
		else if (precision < 0)
		    do scale /= 10; while (++precision < 0);

		if (rndtoeven)
		    return (rint(eval(e->e.o.left) * scale) / scale);
		else {
		    double temp = eval(e->e.o.left);
		    temp *= scale;
		    /* xxx */
		    /*
		    temp = (temp > 0.0 ? floor(temp + 0.5) : ceil(temp - 0.5));
		    */
		    temp = ((temp - floor(temp)) < 0.5 ?
			    floor(temp) : ceil(temp));
		    return (temp / scale);
		}
	    }
	case FV:
	case PV:
	case PMT:	return (finfunc(e->op, eval(e->e.o.left),
				    eval(e->e.o.right->e.o.left),
				    eval(e->e.o.right->e.o.right)));
	case HOUR:	return (dotime(HOUR, eval(e->e.o.left)));
	case MINUTE:	return (dotime(MINUTE, eval(e->e.o.left)));
	case SECOND:	return (dotime(SECOND, eval(e->e.o.left)));
	case MONTH:	return (dotime(MONTH, eval(e->e.o.left)));
	case DAY:	return (dotime(DAY, eval(e->e.o.left)));
	case YEAR:	return (dotime(YEAR, eval(e->e.o.left)));
	case NOW:	return (dotime(NOW, (double)0.0));
	case DTS:	return (dodts((int)eval(e->e.o.left),
				    (int)eval(e->e.o.right->e.o.left),
				    (int)eval(e->e.o.right->e.o.right)));
	case TTS:	return (dotts((int)eval(e->e.o.left),
				    (int)eval(e->e.o.right->e.o.left),
				    (int)eval(e->e.o.right->e.o.right)));
	case STON:	return (doston(seval(e->e.o.left)));
	case EQS:       return (doeqs(seval(e->e.o.right), seval(e->e.o.left)));
	case LMAX:	return dolmax(e);
	case LMIN:	return dolmin(e);
	case NVAL:      return (donval(seval(e->e.o.left),eval(e->e.o.right)));
	case MYROW:	return ((double) (gmyrow + rowoffset));
	case MYCOL:	return ((double) (gmycol + coloffset));
	case LASTROW:	return ((double) maxrow);
	case LASTCOL:	return ((double) maxcol);
	case NUMITER:	return ((double) repct);
	case ERR_:	cellerror = CELLERROR;
			return ((double) 0);
	case PI_:	return ((double) M_PI);
	case BLACK:	return ((double) COLOR_BLACK);
	case RED:	return ((double) COLOR_RED);
	case GREEN:	return ((double) COLOR_GREEN);
	case YELLOW:	return ((double) COLOR_YELLOW);
	case BLUE:	return ((double) COLOR_BLUE);
	case MAGENTA:	return ((double) COLOR_MAGENTA);
	case CYAN:	return ((double) COLOR_CYAN);
	case WHITE:	return ((double) COLOR_WHITE);
	default:	error ("Illegal numeric expression");
			exprerr = 1;
    }
    cellerror = CELLERROR;
    return ((double)0.0);
}

#ifdef SIGVOID
void
#else
int
#endif
eval_fpe() /* Trap for FPE errors in eval */
{
#if defined(i386) && !defined(M_XENIX)
    asm("	fnclex");
    asm("	fwait");
#else
#ifdef IEEE_MATH
    (void)fpsetsticky((fp_except)0);	/* Clear exception */
#endif /* IEEE_MATH */
#ifdef PC
    _fpreset();
#endif
#endif
    /* re-establish signal handler for next time */
    (void) signal(SIGFPE, eval_fpe);
    longjmp(fpe_save, 1);
}

double
fn1_eval(double (*fn)(), double arg)
{
    double res;
    errno = 0;
    res = (*fn)(arg);
    if (errno)
	cellerror = CELLERROR;

    return res;
}

double
fn2_eval(double (*fn)(), double arg1, double arg2)
{
    double res;
    errno = 0;
    res = (*fn)(arg1, arg2);
    if (errno)
	cellerror = CELLERROR;

    return res;
}

/* 
 * Rules for string functions:
 * Take string arguments which they scxfree.
 * All returned strings are assumed to be xalloced.
 */

char *
docat(register char *s1, register char *s2)
{
    register char *p;
    char *arg1, *arg2;

    if (!s1 && !s2)
	return ((char *)0);
    arg1 = s1 ? s1 : "";
    arg2 = s2 ? s2 : "";
    p = scxmalloc((size_t)(strlen(arg1)+strlen(arg2)+1));
    (void) strcpy(p, arg1);
    (void) strcat(p, arg2);
    if (s1)
        scxfree(s1);
    if (s2)
        scxfree(s2);
    return (p);
}

char *
dodate(time_t tloc, char *fmtstr)
{
    char buff[FBUFLEN];
    char *p;

    if (!fmtstr)
	fmtstr = "%a %b %d %H:%M:%S %Y";
    strftime(buff, FBUFLEN, fmtstr, localtime(&tloc));
    p = scxmalloc((size_t)(strlen(buff)+1));
    (void) strcpy(p, buff);
    return (p);
}


char *
dofmt(char *fmtstr, double v)
{
    char buff[FBUFLEN];
    char *p;

    if (!fmtstr)
	return ((char *)0);
    (void) snprintf(buff, FBUFLEN, fmtstr, v);
    p = scxmalloc((size_t)(strlen(buff)+1));
    (void) strcpy(p, buff);
    scxfree(fmtstr);
    return (p);
}


/*
 * Given a command name and a value, run the command with the given value and
 * read and return its first output line (only) as an allocated string, always
 * a copy of se->e.o.s, which is set appropriately first unless external
 * functions are disabled, in which case the previous value is used.  The
 * handling of se->e.o.s and freeing of command is tricky.  Returning an
 * allocated string in all cases, even if null, insures cell expressions are
 * written to files, etc.
 */

#if defined(VMS) || defined(MSDOS)
char *
doext(struct enode *se)
{
    char *command = seval(se->e.o.left);
    double value = eval(se->e.o.right);

    error("Warning: External functions unavailable on VMS");
    cellerror = CELLERROR;	/* not sure if this should be a cellerror */
    if (command)
	scxfree(command);
    return (strcpy(scxmalloc((size_t) 1), "\0"));
}

#else /* VMS */

char *
doext(struct enode *se)
{
    char buff[FBUFLEN];		/* command line/return, not permanently alloc */
    char *command;
    double value;

    command = seval(se->e.o.left);
    value = eval(se->e.o.right);
    if (!extfunc) {
	error("Warning: external functions disabled; using %s value",
		(se->e.o.s && *se->e.o.s) ? "previous" : "null");

	if (command) scxfree(command);
    } else {
	if ((! command) || (! *command)) {
	    error ("Warning: external function given null command name");
	    cellerror = CELLERROR;
	    if (command) scxfree(command);
	} else {
	    FILE *pp;

	    (void) sprintf(buff, "%s %g", command, value); /* build cmd line */
	    scxfree(command);

	    error("Running external function...");
	    (void) refresh();

	    if ((pp = popen(buff, "r")) == (FILE *) NULL) {	/* run it */
		error("Warning: running \"%s\" failed", buff);
		cellerror = CELLERROR;
	    } else {
		if (fgets(buff, sizeof(buff)-1, pp) == NULL)	/* one line */
		    error("Warning: external function returned nothing");
		else {
		    char *cp;

		    error("");				/* erase notice */
		    buff[sizeof(buff)-1] = '\0';

		    if ((cp = strchr(buff, '\n')))	/* contains newline */
			*cp = '\0';			/* end string there */

		    if (!se->e.o.s || strlen(buff) != strlen(se->e.o.s))
			se->e.o.s = scxrealloc(se->e.o.s, strlen(buff));
		    (void) strcpy (se->e.o.s, buff);
			 /* save alloc'd copy */
		}
		(void) pclose(pp);

	    } /* else */
	} /* else */
    } /* else */
    if (se->e.o.s)
	return (strcpy(scxmalloc((size_t) (strlen(se->e.o.s)+1)), se->e.o.s));
    else
	return (strcpy(scxmalloc((size_t)1), ""));
}

#endif /* VMS */


/*
 * Given a string representing a column name and a value which is a column
 * number, return the selected cell's string value, if any.  Even if none,
 * still allocate and return a null string so the cell has a label value so
 * the expression is saved in a file, etc.
 */

char *
dosval(char *colstr, double rowdoub)
{
    struct ent *ep;
    char *llabel;

    llabel = (ep = getent(colstr, rowdoub)) ? (ep -> label) : "";
    return (strcpy(scxmalloc((size_t) (strlen(llabel) + 1)), llabel));
}


/*
 * Substring:  Note that v1 and v2 are one-based to users, but zero-based
 * when calling this routine.
 */

char *
dosubstr(char *s, register int v1, register int v2)
{
    register char *s1, *s2;
    char *p;

    if (!s)
	return ((char *)0);

    if (v2 >= strlen(s))		/* past end */
	v2 =  strlen(s) - 1;		/* to end   */

    if (v1 < 0 || v1 > v2) {		/* out of range, return null string */
	scxfree(s);
	p = scxmalloc((size_t)1);
	p[0] = '\0';
	return (p);
    }
    s2 = p = scxmalloc((size_t)(v2-v1+2));
    s1 = &s[v1];
    for (; v1 <= v2; s1++, s2++, v1++)
	*s2 = *s1;
    *s2 = '\0';
    scxfree(s);
    return(p);
}

/*
 * character casing: make upper case, make lower case
 */

char *
docase(int acase, char *s)
{
    char *p = s;

    if (s == NULL)
	return(NULL);

    if( acase == UPPER ) {
        while( *p != '\0' ) {
           if( islower(*p) )
		*p = toupper(*p);
	   p++;
	}
    }
    else if (acase == LOWER) {
	while (*p != '\0') {
	    if (isupper(*p))
		*p = tolower(*p);
	    p++;
	}
    }
    return (s);
}

/*
 * make proper capitals of every word in a string
 * if the string has mixed case we say the string is lower
 *	and we will upcase only first letters of words
 * if the string is all upper we will lower rest of words.
 */

char *
docapital(char *s)
{
    char *p;
    int skip = 1;
    int AllUpper = 1;

    if (s == NULL)
	return (NULL);
    for (p = s; *p != '\0' && AllUpper != 0; p++)
	if (isalpha(*p) && islower(*p))  AllUpper = 0;
    for (p = s; *p != '\0'; p++) {
	if (!isalnum(*p))
		skip = 1;
	else
	if (skip == 1) {
	    skip = 0;
	    if (islower(*p))
		*p = toupper(*p);
	}
	else	/* if the string was all upper before */
        if (isupper(*p) && AllUpper != 0)
	    *p = tolower(*p);
    }
    return (s);
}

char *
seval(register struct enode *se)
{
    register char *p;

    if (se == (struct enode *)0) return (char *)0;
    switch (se->op) {
	case O_SCONST: p = scxmalloc((size_t)(strlen(se->e.s)+1));
		     (void) strcpy(p, se->e.s);
		     return (p);
	case O_VAR:    {
			struct ent *vp = se->e.v.vp;
			int row, col;
			if (vp && (rowoffset || coloffset)) {
			    row = se->e.v.vf & FIX_ROW ? vp->row
						       : vp->row + rowoffset;
			    col = se->e.v.vf & FIX_COL ? vp->col
						       : vp->col + coloffset;
			    checkbounds(&row, &col);
			    vp = *ATBL(tbl, row, col);
			}
			if (!vp || !vp->label)
			    return (NULL);
			p = scxmalloc((size_t)(strlen(vp->label)+1));
			(void) strcpy(p, vp->label);
			return (p);
	}
	case '#':    return (docat(seval(se->e.o.left), seval(se->e.o.right)));
	case 'f':    {
		     int rtmp = rowoffset;
		     int ctmp = coloffset;
		     char *ret;
		     rowoffset = coloffset = 0;
		     ret = seval(se->e.o.left);
		     rowoffset = rtmp;
		     coloffset = ctmp;
		     return (ret);
		     }
	case 'F':    return (seval(se->e.o.left));
	case IF:
	case '?':    return (eval(se->e.o.left) ? seval(se->e.o.right->e.o.left)
					     : seval(se->e.o.right->e.o.right));
	case DATE:   return (dodate((time_t)(eval(se->e.o.left)),
				 seval(se->e.o.right)));
	case FMT:    return (dofmt(seval(se->e.o.left), eval(se->e.o.right)));
	case UPPER:  return (docase(UPPER, seval(se->e.o.left)));
	case LOWER:  return (docase(LOWER, seval(se->e.o.left)));
	case CAPITAL:return (docapital(seval(se->e.o.left)));
 	case STINDEX: {
	    int r, c;
	    int maxr, maxc;
	    int minr, minc;
	    maxr = se->e.o.left->e.r.right.vp->row;
	    maxc = se->e.o.left->e.r.right.vp->col;
	    minr = se->e.o.left->e.r.left.vp->row;
	    minc = se->e.o.left->e.r.left.vp->col;
	    if (minr>maxr) r = maxr, maxr = minr, minr = r;
	    if (minc>maxc) c = maxc, maxc = minc, minc = c;
	    return dostindex(minr, minc, maxr, maxc, se->e.o.right);
	}
	case EXT:    return (doext(se));
	case SVAL:   return (dosval(seval(se->e.o.left), eval(se->e.o.right)));
	case SUBSTR: return (dosubstr(seval(se->e.o.left),
			    (int)eval(se->e.o.right->e.o.left) - 1,
			    (int)eval(se->e.o.right->e.o.right) - 1));
	case COLTOA: return (strcpy(scxmalloc((size_t)10),
				   coltoa((int)eval(se->e.o.left))));
	case FILENAME: {
		     int n = eval(se->e.o.left);
		     char *s = strrchr(curfile, '/');

		     if (n || s++ == NULL) s = curfile;
		     p = scxmalloc((size_t)(strlen(s)+1));
		     (void) strcpy(p, s);
		     return (p);
	}
	default:
		     error("Illegal string expression");
		     exprerr = 1;
		     return (NULL);
    }
}

/*
 * The graph formed by cell expressions which use other cells's values is not
 * evaluated "bottom up".  The whole table is merely re-evaluated cell by cell,
 * top to bottom, left to right, in RealEvalAll().  Each cell's expression uses
 * constants in other cells.  However, RealEvalAll() notices when a cell gets a
 * new numeric or string value, and reports if this happens for any cell.
 * EvalAll() repeats calling RealEvalAll() until there are no changes or the
 * evaluation count expires.
 */

int propagation = 10;	/* max number of times to try calculation */
int repct = 1;		/* Make repct a global variable so that the 
				function @numiter can access it */

void
setiterations(int i)
{
    if (i < 1) {
	error("iteration count must be at least 1");
	propagation = 1;
    } else
	propagation = i;
}

void
EvalAll()
{
    int lastcnt, pair, v;
  
    repct = 1;
    (void) signal(SIGFPE, eval_fpe);

    while ((lastcnt = RealEvalAll()) && (++repct <= propagation));
    if ((propagation > 1) && (lastcnt > 0))
 	error("Still changing after %d iterations", repct - 1);

    if (usecurses && color && has_colors()) {
	for (pair = 0; pair < 8; pair++) {
	    cellerror = CELLOK;
	    if (cpairs[pair] && cpairs[pair]->expr) {
		v = (int) eval(cpairs[pair]->expr);
		cpairs[pair]->fg = v & 7;
		cpairs[pair]->bg = (v >> 3) & 7;
		init_pair(pair + 1, cpairs[pair]->fg, cpairs[pair]->bg);
	    }
	    /* Can't see to fix the problem if color 1 has an error, so
	     * turn off color in that case.
	     */
	    if (pair == 0 && cellerror) {
		color = 0;
		attron(COLOR_PAIR(0));
		color_set(0, NULL);
		error("Error in color 1: color turned off");
	    }
	}
    }

    (void) signal(SIGFPE, doquit);
}

/*
 * Evaluate all cells which have expressions and alter their numeric or string
 * values.  Return the number of cells which changed.
 */

int 
RealEvalAll()
{
    register int i,j;
    int chgct = 0;
    register struct ent *p;

    if (calc_order == BYROWS) {
	for (i=0; i<=maxrow; i++)
	    for (j=0; j<=maxcol; j++)
		if ((p = *ATBL(tbl,i,j)) && p->expr)
		    RealEvalOne(p,i,j,&chgct);
    }
    else if (calc_order == BYCOLS) {
	for (j=0; j<=maxcol; j++) {
	    for (i=0; i<=maxrow; i++)
		if ((p = *ATBL(tbl,i,j)) && p->expr)
		    RealEvalOne(p,i,j,&chgct);
	}
    }
    else error("Internal error calc_order");
 
    return (chgct);
}

void
RealEvalOne(register struct ent *p, int i, int j, int *chgct)
{
    gmyrow=i; gmycol=j;

    if (p->flags & is_strexpr) {
	char *v;
	if (setjmp(fpe_save)) {
	    error("Floating point exception %s", v_name(i, j));
	    cellerror = CELLERROR;
	    v = "";
	} else {
	    cellerror = CELLOK;
	    v = seval(p->expr);
	}
	p->cellerror = cellerror;
	if (!v && !p->label) /* Everything's fine */
	    return;
	if (!p->label || !v || strcmp(v, p->label) != 0 || cellerror) {
	    (*chgct)++;
	    p->flags |= is_changed;
	    changed++;
	}
	if (p->label)
	    scxfree(p->label);
	p->label = v;
    } else {
	double v;
	if (setjmp(fpe_save)) {
	    error("Floating point exception %s", v_name(i, j));
	    cellerror = CELLERROR;
	    v = (double)0.0;
	} else {
	    cellerror = CELLOK;
	    v = eval(p->expr);
	    if (cellerror == CELLOK && !finite(v))
		cellerror = CELLERROR;
	}
	if ((cellerror != p->cellerror) || (v != p->v)) {
	    p->cellerror = cellerror;
	    p->v = v;
	    if (!cellerror)	/* don't keep eval'ing an error */
		(*chgct)++;
	    p->flags |= is_changed|is_valid;
	    changed++;
	}
    }
}

struct enode *
new(int op, struct enode *a1, struct enode *a2)
{
    register struct enode *p;
    if (freeenodes) {
    	p = freeenodes;
	freeenodes = p->e.o.left;
    } else
	p = (struct enode *) scxmalloc((size_t)sizeof(struct enode));
    p->op = op;
    p->e.o.left = a1;
    p->e.o.right = a2;
    p->e.o.s = NULL;
    return p;
}

struct enode *
new_var(int op, struct ent_ptr a1)
{
    register struct enode *p;
    if (freeenodes) {
    	p = freeenodes;
	freeenodes = p->e.o.left;
    } else
	p = (struct enode *) scxmalloc((size_t)sizeof(struct enode));
    p->op = op;
    p->e.v = a1;
    return p;
}

struct enode *
new_range(int op, struct range_s a1)
{
    register struct enode *p;
    if (freeenodes)
    {	p = freeenodes;
	freeenodes = p->e.o.left;
    }
    else
	p = (struct enode *) scxmalloc((size_t)sizeof(struct enode));
    p->op = op;
    p->e.r = a1;
    return p;
}

struct enode *
new_const(int op, double a1)
{
    register struct enode *p;
    if (freeenodes) {	/* reuse an already free'd enode */
	p = freeenodes;
	freeenodes = p->e.o.left;
    } else
	p = (struct enode *) scxmalloc((size_t)sizeof(struct enode));
    p->op = op;
    p->e.k = a1;
    return p;
}

struct enode *
new_str(char *s)
{
    register struct enode *p;

    if (freeenodes) {	/* reuse an already free'd enode */
    	p = freeenodes;
	freeenodes = p->e.o.left;
    } else
	p = (struct enode *) scxmalloc((size_t)sizeof(struct enode));
    p->op = O_SCONST;
    p->e.s = s;
    return (p);
}

void
copy(struct ent *dv1, struct ent *dv2, struct ent *v1, struct ent *v2)
{
    struct ent *p;
    struct ent *n;
    static int minsr = -1, minsc = -1;
    static int maxsr = -1, maxsc = -1;
    int mindr, mindc;
    int maxdr, maxdc;
    int vr, vc;
    int r, c;
    int deltar, deltac;

    if (dv1) {
	mindr = dv1->row;
	mindc = dv1->col;
	maxdr = dv2->row;
	maxdc = dv2->col;
	if (mindr > maxdr) r = maxdr, maxdr = mindr, mindr = r;
	if (mindc > maxdc) c = maxdc, maxdc = mindc, mindc = c;
    } else {
	if (showrange) {
	    showrange = 0;
	    mindr = showsr < currow ? showsr : currow;
	    mindc = showsc < curcol ? showsc : curcol;
	    maxdr = showsr > currow ? showsr : currow;
	    maxdc = showsc > curcol ? showsc : curcol;
	} else if (v1) {
	    /* Set up the default source range for the "c." command. */
	    minsr = maxsr = v1->row;
	    minsc = maxsc = v1->col;
	    return;
	} else {
	    mindr = maxdr = currow;
	    mindc = maxdc = curcol;
	}
    }

    if (v1) {
	minsr = v1->row;
	minsc = v1->col;
	maxsr = v2->row;
	maxsc = v2->col;
	if (minsr > maxsr) r = maxsr, maxsr = minsr, minsr = r;
	if (minsc > maxsc) c = maxsc, maxsc = minsc, minsc = c;
    } else if (dv1 == NULL || v2 != NULL) {
	if (qbuf && delbuf[qbuf]) {
	    delbuf[++dbidx] = delbuf[qbuf];
	    delbuffmt[dbidx] = delbuffmt[qbuf];
	} else if (dbidx < 0) return;
	minsr = maxrow;
	minsc = maxcol;
	maxsr = 0;
	maxsc = 0;
	for (p = delbuf[dbidx]; p; p = p->next) {
	    if (p->row < minsr) minsr = p->row;
	    if (p->row > maxsr) maxsr = p->row;
	    if (p->col < minsc) minsc = p->col;
	    if (p->col > maxsc) maxsc = p->col;
	}
    } else if (showrange && !(showsr == currow && showsc == curcol &&
	    mindr == currow && mindc == curcol &&
	    maxdr == currow && maxdc == curcol)) {
	minsr = showsr < currow ? showsr : currow;
	minsc = showsc < curcol ? showsc : curcol;
	maxsr = showsr > currow ? showsr : currow;
	maxsc = showsc > curcol ? showsc : curcol;
    } else
	if (minsr == -1) return;

    checkbounds(&maxdr, &maxdc);

    if (maxdr - mindr < maxsr - minsr) maxdr = mindr + (maxsr - minsr);
    if (maxdc - mindc < maxsc - minsc) maxdc = mindc + (maxsc - minsc);
    if (dv1 && (v1 || !v2))
	yank_area(minsr, minsc, maxsr, maxsc);
    erase_area(mindr, mindc, maxdr, maxdc, 0);
    sync_refs();
    flush_saved();

    error("Copying...");
    if (!loading)
	refresh();
    p = delbuf[dbidx];
    if (minsr == maxsr && minsc == maxsc) {
	/* Source is a single cell */
	for (deltar = mindr - p->row; deltar <= maxdr - p->row; deltar++)
	    for (deltac = mindc - p->col; deltac <= maxdc - p->col; deltac++)
		copydbuf(deltar, deltac);
    } else if (minsr == maxsr) {
	/* Source is a single row */
	deltac = mindc - p->col;
	for (deltar = mindr - p->row; deltar <= maxdr - p->row; deltar++)
	    copydbuf(deltar, deltac);
    } else if (minsc == maxsc) {
	/* Source is a single column */
	deltar = mindr - p->row;
	for (deltac = mindc - p->col; deltac <= maxdc - p->col; deltac++)
	    copydbuf(deltar, deltac);
    } else {
	/* Everything else */
	deltar = mindr - p->row;
	deltac = mindc - p->col;
	copydbuf(deltar, deltac);
    }

    if (dv1 && (v1 || !v2)) {
	sync_refs();
	flush_saved();
    }

    if (dv1 == NULL) {
	if (qbuf && delbuf[qbuf]) {
	    delbuf[dbidx] = NULL;
	    delbuffmt[dbidx--] = NULL;
	}
	qbuf = 0;
    }
    error("Copy done.");
}

void
copydbuf(int deltar, int deltac)
{
    int vr, vc;
    struct ent *p = delbuf[dbidx];
    struct ent *n;

    while (p) {
	vr = p->row + deltar;
	vc = p->col + deltac;
	n = lookat(vr, vc);
	if (n->flags&is_locked) continue;
	copyent(n, p, deltar, deltac, 0, 0, maxrow, maxcol, 0);
	p = p->next;
    }
}

/* ERASE a Range of cells */
void
eraser(struct ent *v1, struct ent *v2)
{
    int i;
    struct ent *obuf = NULL;

    if (dbidx < 0) dbidx++;
    delbuf[dbidx] = delbuf[DELBUFSIZE - 1];
    delbuf[DELBUFSIZE - 1] = NULL;
    delbuffmt[dbidx] = delbuffmt[DELBUFSIZE - 1];
    delbuffmt[DELBUFSIZE - 1] = NULL;
    for (i = dbidx + 1; i < DELBUFSIZE; i++) {
	if (delbuf[i] == delbuf[dbidx]) {
	    delbuf[dbidx] = NULL;
	    delbuffmt[dbidx] = NULL;
	    break;
	}
    }
    flush_saved();
    if (qbuf) {
	if (dbidx < 0) dbidx++;
	delbuf[dbidx] = delbuf[qbuf];
	delbuffmt[dbidx] = delbuffmt[qbuf];
	flush_saved();
	obuf = delbuf[qbuf];	/* orig. contents of the del. buffer */
    }
    erase_area(v1->row, v1->col, v2->row, v2->col, 0);
    sync_refs();
    for (i = 0; i < DELBUFSIZE; i++)
	if ((obuf && delbuf[i] == obuf) || (qbuf && i == qbuf)) {
	    delbuf[i] = delbuf[dbidx];
	    delbuffmt[i] = delbuffmt[dbidx];
	}
    qbuf = 0;
    for (i = DELBUFSIZE - 1; i > DELBUFSIZE - 9; i--) {
	delbuf[i] = delbuf[i-1];
	delbuffmt[i] = delbuffmt[i-1];
    }
    delbuf[DELBUFSIZE - 9] = delbuf[dbidx];
    delbuffmt[DELBUFSIZE - 9] = delbuffmt[dbidx];
    FullUpdate++;
    modflg++;
}

/* YANK a Range of cells */
void
yankr(struct ent *v1, struct ent *v2)
{
    int i, qtmp;
    struct ent *obuf = NULL;

    if (dbidx < 0) dbidx++;
    delbuf[dbidx] = delbuf[DELBUFSIZE - 10];
    delbuf[DELBUFSIZE - 10] = NULL;
    delbuffmt[dbidx] = delbuffmt[DELBUFSIZE - 10];
    delbuffmt[DELBUFSIZE - 10] = NULL;
    for (i = dbidx + 1; i < DELBUFSIZE; i++) {
	if (delbuf[i] == delbuf[dbidx]) {
	    delbuf[dbidx] = NULL;
	    delbuffmt[dbidx] = NULL;
	    break;
	}
    }
    flush_saved();
    if (qbuf) {
	if (dbidx < 0) dbidx++;
	delbuf[dbidx] = delbuf[qbuf];
	delbuffmt[dbidx] = delbuffmt[qbuf];
	flush_saved();
	obuf = delbuf[qbuf];	/* orig. contents of the del. buffer */
    }
    qtmp = qbuf;
    qbuf = 0;
    yank_area(v1->row, v1->col, v2->row, v2->col);
    qbuf = qtmp;
    for (i = 0; i < DELBUFSIZE; i++)
	if ((obuf && delbuf[i] == obuf) || (qbuf && i == qbuf)) {
	    delbuf[i] = delbuf[dbidx];
	    delbuffmt[i] = delbuffmt[dbidx];
	}
    qbuf = 0;
    delbuf[DELBUFSIZE - 10] = delbuf[dbidx];
    delbuffmt[DELBUFSIZE - 10] = delbuffmt[dbidx];
}

/* MOVE a Range of cells */
void
mover(struct ent *d, struct ent *v1, struct ent *v2)
{
    move_area(d->row, d->col, v1->row, v1->col, v2->row, v2->col);
    sync_refs();
    FullUpdate++;
}

/* Goto subroutines */

void
g_free()
{
    switch (gs.g_type) {
	case G_STR:
	case G_NSTR: scxfree(gs.g_s); break;
	default: break;
    }
    gs.g_type = G_NONE;
    gs.errsearch = 0;
}

/* repeat the last goto command */
void
go_last()
{
    int num = 0;

    switch (gs.g_type) {
	case G_NONE:
	    error("Nothing to repeat"); break;
	case G_NUM:
	    num_search(gs.g_n, gs.g_row, gs.g_col,
		gs.g_lastrow, gs.g_lastcol, gs.errsearch);
	    break;
	case G_CELL:
	    moveto(gs.g_row, gs.g_col, gs.g_lastrow, gs.g_lastcol,
		gs.strow, gs.stcol);
	    break;
	case G_XSTR: 
	    num++;
	case G_NSTR: 
	    num++;
	case G_STR: 
	    gs.g_type = G_NONE;	/* Don't free the string */
	    str_search(gs.g_s, gs.g_row, gs.g_col, gs.g_lastrow, gs.g_lastcol,
		    num); 
	    break;

	default: error("go_last: internal error");
    }
}

/* Place the cursor on a given cell.  If cornerrow >= 0, place the cell
 * at row cornerrow and column cornercol in the upper left corner of the
 * screen if possible.
 */
void
moveto(int row, int col, int lastrow, int lastcol, int cornerrow,
    int cornercol)
{
    register int i;

    if (!loading && row != -1 && (row != currow || col != curcol))
	remember(0);

    currow = row;
    curcol = col;
    g_free();
    gs.g_type = G_CELL;
    gs.g_row = currow;
    gs.g_col = curcol;
    gs.g_lastrow = lastrow;
    gs.g_lastcol = lastcol;
    gs.strow = cornerrow;
    gs.stcol = cornercol;
    if (cornerrow >= 0) {
	strow = cornerrow;
	stcol = cornercol;
	gs.stflag = 1;
    } else
	gs.stflag = 0;
    for (rowsinrange = 0, i = row; i <= lastrow; i++) {
	if (row_hidden[i])
	    continue;
	rowsinrange++;
    }
    for (colsinrange = 0, i = col; i <= lastcol; i++) {
	if (col_hidden[i])
	    continue;
	colsinrange += fwidth[i];
    }
    FullUpdate++;
    if (loading) {
	update(1);
	changed = 0;
    } else
	remember(1);
}

/*
 * 'goto' either a given number,'error', or 'invalid' starting at currow,curcol
 */
void
num_search(double n, int firstrow, int firstcol, int lastrow,
    int lastcol, int errsearch)
{
    register struct ent *p;
    register int r,c;
    int	endr, endc;

    if (!loading)
	remember(0);

    g_free();
    gs.g_type = G_NUM;
    gs.g_n = n;
    gs.g_row = firstrow;
    gs.g_col = firstcol;
    gs.g_lastrow = lastrow;
    gs.g_lastcol = lastcol;
    gs.errsearch = errsearch;

    if (currow >= firstrow && currow <= lastrow &&
	    curcol >= firstcol && curcol <= lastcol) {
	endr = currow;
	endc = curcol;
    } else {
	endr = lastrow;
	endc = lastcol;
    }
    r = endr;
    c = endc;
    while (1) {
	if (c < lastcol)
	    c++;
	else {
	    if (r < lastrow) {
		while (++r < lastrow && row_hidden[r]) /* */;
		c = firstcol;
	    } else {
		r = firstrow;
		c = firstcol;
	    }
	}
	p = *ATBL(tbl, r, c);
	if (!col_hidden[c] && p && (p->flags & is_valid) &&
		(errsearch || (p->v == n)) && (!errsearch ||
		(p->cellerror == errsearch)))	/* CELLERROR vs CELLINVALID */
	    break;
	if (r == endr && c == endc) {
	    if (errsearch)
		error("no %s cell found", errsearch == CELLERROR ? "ERROR" :
		      "INVALID");
	    else
		error("Number not found");
	    return;
	}
    }
		
    currow = r;
    curcol = c;
    rowsinrange = 1;
    colsinrange = fwidth[curcol];
    if (loading) {
	update(1);
	changed = 0;
    } else
	remember(1);
}

/* 'goto' a cell containing a matching string */
void
str_search(char *s, int firstrow, int firstcol, int lastrow, int lastcol,
	int num)
{
    struct ent	*p;
    int		r, c;
    int		endr, endc;
    char	*tmp;
#if defined(REGCOMP)
    regex_t	preg;
    int		errcode;
#endif

    if (!loading)
	remember(0);

#if defined(REGCOMP)
    if ((errcode = regcomp(&preg, s, REG_EXTENDED))) {
	scxfree(s);
	tmp = scxmalloc((size_t)160);
	regerror(errcode, &preg, tmp, sizeof(tmp));
	error(tmp);
	scxfree(tmp);
	return;
    }
#endif
#if defined(RE_COMP)
    if ((tmp = re_comp(s)) != NULL) {
	scxfree(s);
	error(tmp);
	return;
    }
#endif
#if defined(REGCMP)
    if ((tmp = regcmp(s, NULL)) == NULL) {
	scxfree(s);
	cellerror = CELLERROR;
	error("Invalid search string");
	return;
    }
#endif
    g_free();
    gs.g_type = G_STR + num;
    gs.g_s = s;
    gs.g_row = firstrow;
    gs.g_col = firstcol;
    gs.g_lastrow = lastrow;
    gs.g_lastcol = lastcol;
    if (currow >= firstrow && currow <= lastrow &&
	    curcol >= firstcol && curcol <= lastcol) {
	endr = currow;
	endc = curcol;
    } else {
	endr = lastrow;
	endc = lastcol;
    }
    r = endr;
    c = endc;
    while (1) {
	if (c < lastcol) {
	    c++;
	} else {
	    if (r < lastrow) {
		while (++r < lastrow && row_hidden[r]) /* */;
		c = firstcol;
	    } else {
		r = firstrow;
		c = firstcol;
	    }
	}
	p = *ATBL(tbl, r, c);
	if (gs.g_type == G_NSTR) {
	    *line = '\0';
	    if (p) {
		if (p->cellerror)
		    sprintf(line, "%s", p->cellerror == CELLERROR ?
			    "ERROR" : "INVALID");
		else if (p->flags & is_valid) {
		    if (p->format) {
			if (*(p->format) == ctl('d')) {
			    time_t i = (time_t) (p->v);
			    strftime(line, sizeof(line), (p->format)+1,
				localtime(&i));
			} else
			    format(p->format, precision[c], p->v, line,
				    sizeof(line));
		    } else
			engformat(realfmt[c], fwidth[c], precision[c],
				p->v, line, sizeof(line));
		}
	    }
	} else if (gs.g_type == G_XSTR) {
	    *line = '\0';
	    if (p && p->expr) {
		linelim = 0;
		decompile(p->expr, 0);	/* set line to expr */
		line[linelim] = '\0';
		if (*line == '?')
		    *line = '\0';
	    }
	}
	if (!col_hidden[c])
	    if (gs.g_type == G_STR) {
		if (p && p->label
#if defined(REGCOMP)
			&& (regexec(&preg, p->label, 0, NULL, 0) == 0))
#else
#if defined(RE_COMP)
			&& (re_exec(p->label) != 0))
#else
#if defined(REGCMP)
			&& (regex(tmp, p->label) != (char *)0))
#else
			&& (strcmp(s, p->label) == 0))
#endif
#endif
#endif
		    break;
	    } else			/* gs.g_type != G_STR */
		if (*line != '\0'
#if defined(REGCOMP)
			&& (regexec(&preg, line, 0, NULL, 0) == 0))
#else
#if defined(RE_COMP)
			&& (re_exec(line) != 0))
#else
#if defined(REGCMP)
			&& (regex(tmp, line) != (char *)0))
#else
			&& (strcmp(s, line) == 0))
#endif
#endif
#endif
		    break;
	if (r == endr && c == endc) {
	    error("String not found");
#if defined(REGCOMP)
	    regfree(&preg);
#endif
#if defined(REGCMP)
	    free(tmp);
#endif
	    linelim = -1;
	    return;
	}
    }
    linelim = -1;
    currow = r;
    curcol = c;
    rowsinrange = 1;
    colsinrange = fwidth[curcol];
#if defined(REGCOMP)
    regfree(&preg);
#endif
#if defined(REGCMP)
    free(tmp);
#endif
    if (loading) {
	update(1);
	changed = 0;
    } else
	remember(1);
}

/* fill a range with constants */
void
fill(struct ent *v1, struct ent *v2, double start, double inc)
{
    int r, c;
    register struct ent *n;
    int maxr, maxc;
    int minr, minc;

    maxr = v2->row;
    maxc = v2->col;
    minr = v1->row;
    minc = v1->col;
    if (minr>maxr) r = maxr, maxr = minr, minr = r;
    if (minc>maxc) c = maxc, maxc = minc, minc = c;
    checkbounds(&maxr, &maxc);
    if (minr < 0) minr = 0;
    if (minc < 0) minc = 0;

    FullUpdate++;
    if (calc_order == BYROWS) {
    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++) {
	    n = lookat(r, c);
	    if (n->flags&is_locked) continue;
	    (void) clearent(n);
	    n->v = start;
	    start += inc;
	    n->flags |= (is_changed|is_valid);
	    n->flags &= ~(is_cleared);
	}
    }
    else if (calc_order == BYCOLS) {
    for (c = minc; c<=maxc; c++)
	for (r = minr; r<=maxr; r++) {
	    n = lookat(r, c);
	    (void) clearent(n);
	    n->v = start;
	    start += inc;
	    n->flags |= (is_changed|is_valid);
	    n->flags &= ~(is_cleared);
	}
    }
    else error(" Internal error calc_order");
    changed++;
}

/* lock a range of cells */

void
lock_cells(struct ent *v1, struct ent *v2)
{
    int r, c;
    register struct ent *n;
    int maxr, maxc;
    int minr, minc;

    maxr = v2->row;
    maxc = v2->col;
    minr = v1->row;
    minc = v1->col;
    if (minr>maxr) r = maxr, maxr = minr, minr = r;
    if (minc>maxc) c = maxc, maxc = minc, minc = c;
    checkbounds(&maxr, &maxc);
    if (minr < 0) minr = 0;
    if (minc < 0) minc = 0;

    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++) {
	    n = lookat(r, c);
	    n->flags |= is_locked;
	}
}

/* unlock a range of cells */

void
unlock_cells(struct ent *v1, struct ent *v2)
{
    int r, c;
    register struct ent *n;
    int maxr, maxc;
    int minr, minc;

    maxr = v2->row;
    maxc = v2->col;
    minr = v1->row;
    minc = v1->col;
    if (minr>maxr) r = maxr, maxr = minr, minr = r;
    if (minc>maxc) c = maxc, maxc = minc, minc = c;
    checkbounds(&maxr, &maxc);
    if (minr < 0) minr = 0;
    if (minc < 0) minc = 0;

    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++) {
	    n = lookat(r, c);
	    n->flags &= ~is_locked;
	}
}

/* set the numeric part of a cell */
void
let(struct ent *v, struct enode *e)
{
    double val;
    unsigned isconstant = constant(e);

    if (locked_cell(v->row, v->col))
	return;
    if (v->row == currow && v->col == curcol)
	cellassign = 1;
    if (loading && !isconstant)
	val = (double)0.0;
    else {
	exprerr = 0;
	(void) signal(SIGFPE, eval_fpe);
	if (setjmp(fpe_save)) {
	    error("Floating point exception in cell %s", v_name(v->row, v->col));
	    val = (double)0.0;
	    cellerror = CELLERROR;
	} else {
	    cellerror = CELLOK;
	    val = eval(e);
	}
	if (v->cellerror != cellerror) {
	    v->flags |= is_changed;
	    changed++;
	    modflg++;
	    FullUpdate++;
	    v->cellerror = cellerror;
	}
	(void) signal(SIGFPE, doquit);
	if (exprerr) {
	    efree(e);
	    return;
	}
    }

    if (isconstant) {
	/* prescale input unless it has a decimal */
	if (!loading && !decimal && (prescale < (double)0.9999999))
	    val *= prescale;
	decimal = FALSE;

	v->v = val;

	if (!(v->flags & is_strexpr)) {
            efree(v->expr);
	    v->expr = (struct enode *)0;
	}
	efree(e);
    } else {
	efree(v->expr);
	v->expr = e;
	v->flags &= ~is_strexpr;
    }

    changed++; modflg++;
    v->flags |= (is_changed|is_valid);
    
    if (!loading) {
	int i;

	for (i = 36; i > 28; i--) {
	    savedrow[i] = savedrow[i-1];
	    savedcol[i] = savedcol[i-1];
	    savedstrow[i] = savedstrow[i-1];
	    savedstcol[i] = savedstcol[i-1];
	}

	savedrow[28] = v->row;
	savedcol[28] = v->col;
	savedstrow[28] = savedstrow[27];
	savedstcol[28] = savedstcol[27];
    }
}

void
slet(struct ent *v, struct enode *se, int flushdir)
{
    char *p;

    if (locked_cell(v->row, v->col))
	return;
    if (v->row == currow && v->col == curcol)
	cellassign = 1;
    exprerr = 0;
    (void) signal(SIGFPE, eval_fpe);
    if (setjmp(fpe_save)) {
	error ("Floating point exception in cell %s", v_name(v->row, v->col));
	cellerror = CELLERROR;
	p = "";
    } else {
	cellerror = CELLOK;
	p = seval(se);
    }
    if (v->cellerror != cellerror) {
    	v->flags |= is_changed;
	changed++;	modflg++;
	FullUpdate++;
	v->cellerror = cellerror;
    }
    (void) signal(SIGFPE, doquit);
    if (exprerr) {
	efree(se);
	return;
    }
    if (!loading) {
	int i;

	for (i = 36; i > 28; i--) {
	    savedrow[i] = savedrow[i-1];
	    savedcol[i] = savedcol[i-1];
	    savedstrow[i] = savedstrow[i-1];
	    savedstcol[i] = savedstcol[i-1];
	}

	savedrow[28] = v->row;
	savedcol[28] = v->col;
	savedstrow[28] = savedstrow[27];
	savedstcol[28] = savedstcol[27];
    }
    if (constant(se)) {
	label(v, p, flushdir);
	if (p)
	    scxfree(p);
	efree(se);
	if (v->flags & is_strexpr) {
            efree(v->expr);
	    v->expr = (struct enode *)0;
	    v->flags &= ~is_strexpr;
	}
	return;
    }
    efree(v->expr);
    v->expr = se;
    v->flags |= (is_changed|is_strexpr);
    if (flushdir < 0) v->flags |= is_leftflush;

    if (flushdir == 0)
	v->flags |= is_label;
    else v->flags &= ~is_label;

    FullUpdate++;
    changed++;
    modflg++;
}

void
format_cell(struct ent *v1, struct ent *v2, char *s)
{
    int r, c;
    register struct ent *n;
    int maxr, maxc;
    int minr, minc;

    maxr = v2->row;
    maxc = v2->col;
    minr = v1->row;
    minc = v1->col;
    if (minr>maxr) r = maxr, maxr = minr, minr = r;
    if (minc>maxc) c = maxc, maxc = minc, minc = c;
    checkbounds(&maxr, &maxc);
    if (minr < 0) minr = 0;
    if (minc < 0) minc = 0;

    FullUpdate++;
    modflg++;

    for (r = minr; r <= maxr; r++)
	for (c = minc; c <= maxc; c++) {
	    n = lookat(r, c);
	    if (locked_cell(n->row, n->col))
		continue;
	    if (n->format)
		scxfree(n->format);
	    n->format = 0;
	    if (s && *s != '\0')
		n->format = strcpy(scxmalloc((unsigned)(strlen(s)+1)), s);
	    n->flags |= is_changed;
	}
}

void
hide_row(int arg)
{
    if (arg < 0) {
	error("Invalid Range");
	return;
    }
    if (arg >= maxrows-1) {
	if (!growtbl(GROWROW, arg+1, 0)) {
	    error("You can't hide the last row");
	    return;
	}
    }
    FullUpdate++;
    row_hidden[arg] = TRUE;
}

void
hide_col(int arg)
{
    if (arg < 0) {
	error("Invalid Range");
	return;
    }
    if (arg >= maxcols-1) {
    	if ((arg >= ABSMAXCOLS-1) || !growtbl(GROWCOL, 0, arg+1)) {
	    error("You can't hide the last col");
	    return;
	}
    }
    FullUpdate++;
    col_hidden[arg] = TRUE;
}

void
clearent(struct ent *v)
{
    if (!v)
	return;
    label(v,"",-1);
    v->v = (double)0;
    if (v->expr)
	efree(v->expr);
    v->expr = NULL;
    if (v->format)
	scxfree(v->format);
    v->format = NULL;
    v->flags = (is_changed|is_cleared);
    changed++;
    modflg++;
}

/*
 * Say if an expression is a constant (return 1) or not.
 */
int
constant(register struct enode *e)
{
    return (
	 e == NULL
	 || e->op == O_CONST
	 || e->op == O_SCONST
	 || e->op == 'm' && constant(e->e.o.left)
	 || (
	     e->op != O_VAR
	     && !(e->op & REDUCE)
	     && constant(e->e.o.left)
	     && constant(e->e.o.right)
	     && e->op != EXT	 /* functions look like constants but aren't */
	     && e->op != NVAL
	     && e->op != SVAL
	     && e->op != NOW
	     && e->op != MYROW
	     && e->op != MYCOL
	     && e->op != LASTROW
	     && e->op != LASTCOL
	     && e->op != NUMITER
	     && e->op != FILENAME
             && optimize
	)
    );
}

void
efree(struct enode *e)
{
    if (e) {
	if (e->op != O_VAR && e->op != O_CONST && e->op != O_SCONST
		&& !(e->op & REDUCE)) {
	    efree(e->e.o.left);
	    efree(e->e.o.right);
	}
	if (e->op == O_SCONST && e->e.s)
	    scxfree(e->e.s);
	else if (e->op == EXT && e->e.o.s)
	    scxfree(e->e.o.s);
	e->e.o.left = freeenodes;
	freeenodes = e;
    }
}

void
label(register struct ent *v, register char *s, int flushdir)
{
    if (v) {
	if (flushdir==0 && v->flags&is_valid) {
	    register struct ent *tv;
	    if (v->col>0 && ((tv=lookat(v->row,v->col-1))->flags&is_valid)==0)
		v = tv, flushdir = 1;
	    else if (((tv=lookat(v->row,v->col+1))->flags&is_valid)==0)
		v = tv, flushdir = -1;
	    else flushdir = -1;
	}
	if (v->label) scxfree((char *)(v->label));
	if (s && s[0]) {
	    v->label = scxmalloc((unsigned)(strlen(s)+1));
	    (void) strcpy (v->label, s);
	} else
	    v->label = (char *)0;
	if (flushdir<0) v->flags |= is_leftflush;
	else v->flags &= ~is_leftflush;
	if (flushdir==0) v->flags |= is_label;
	else v->flags &= ~is_label;
	FullUpdate++;
	modflg++;
    }
}

void
decodev(struct ent_ptr v)
{
	struct range *r;

	if (!v.vp || v.vp->flags & is_deleted)
	    (void) sprintf(line + linelim, "@ERR");
	else if (!find_range((char *)0, 0, v.vp, v.vp, &r) && !r->r_is_range)
	    (void) sprintf(line+linelim, "%s", r->r_name);
	else {
	    (void) sprintf(line+linelim, "%s%s%s%d",
		v.vf & FIX_COL ? "$" : "",
		coltoa(v.vp->col),
		v.vf & FIX_ROW ? "$" : "",
		v.vp->row);
	}
	linelim += strlen(line+linelim);
}

char *
coltoa(int col)
{
    static char rname[3];
    register char *p = rname;

    if (col > 25) {
	*p++ = col/26 + 'A' - 1;
	col %= 26;
    }
    *p++ = col+'A';
    *p = '\0';
    return (rname);
}

/*
 *	To make list elements come out in the same order
 *	they were entered, we must do a depth-first eval
 *	of the ELIST tree
 */
static void
decompile_list(struct enode *p)
{
	if (!p) return;
	decompile_list(p->e.o.left);	/* depth first */
        decompile(p->e.o.right, 0);
	line[linelim++] = ',';
}

void
decompile(register struct enode *e, int	priority)
{
    register char *s;
    if (e) {
	int mypriority;
	switch (e->op) {
	default: mypriority = 99; break;
	case ';': mypriority = 1; break;
	case '?': mypriority = 2; break;
	case ':': mypriority = 3; break;
	case '|': mypriority = 4; break;
	case '&': mypriority = 5; break;
	case '<': case '=': case '>': mypriority = 6; break;
	case '+': case '-': case '#': mypriority = 8; break;
	case '*': case '/': case '%': mypriority = 10; break;
	case '^': mypriority = 12; break;
	}
	if (mypriority<priority) line[linelim++] = '(';
	switch (e->op) {
	case 'f':	for (s="@fixed "; (line[linelim++] = *s++); );
			linelim--;
			decompile(e->e.o.left, 30);
			break;
	case 'F':	for (s="(@fixed)"; (line[linelim++] = *s++); );
			linelim--;
			decompile(e->e.o.left, 30);
			break;
	case 'm':	line[linelim++] = '-';
			decompile(e->e.o.left, 30);
			break;
	case '!':	line[linelim++] = '!';
			decompile(e->e.o.left, 30);
			break;
	case O_VAR:	decodev(e->e.v);
			break;
	case O_CONST:	(void) sprintf(line+linelim, "%.15g", e->e.k);
			linelim += strlen(line+linelim);
			break;
	case O_SCONST:	(void) sprintf(line+linelim, "\"%s\"", e->e.s);
			linelim += strlen(line+linelim);
			break;

	case SUM	: index_arg("@sum", e); break;
	case PROD	: index_arg("@prod", e); break;
	case AVG	: index_arg("@avg", e); break;
	case COUNT	: index_arg("@count", e); break;
	case STDDEV	: index_arg("@stddev", e); break;
	case MAX	: index_arg("@max", e); break;
	case MIN	: index_arg("@min", e); break;
	case REDUCE | 'R': range_arg("@rows(", e); break;
	case REDUCE | 'C': range_arg("@cols(", e); break;

	case ABS:	one_arg("@abs(", e); break;
	case ACOS:	one_arg("@acos(", e); break;
	case ASIN:	one_arg("@asin(", e); break;
	case ATAN:	one_arg("@atan(", e); break;
	case ATAN2:	two_arg("@atan2(", e); break;
	case CEIL:	one_arg("@ceil(", e); break;
	case COS:	one_arg("@cos(", e); break;
	case EXP:	one_arg("@exp(", e); break;
	case FABS:	one_arg("@fabs(", e); break;
	case FLOOR:	one_arg("@floor(", e); break;
	case HYPOT:	two_arg("@hypot(", e); break;
	case LOG:	one_arg("@ln(", e); break;
	case LOG10:	one_arg("@log(", e); break;
	case POW:	two_arg("@pow(", e); break;
	case SIN:	one_arg("@sin(", e); break;
	case SQRT:	one_arg("@sqrt(", e); break;
	case TAN:	one_arg("@tan(", e); break;
	case DTR:	one_arg("@dtr(", e); break;
	case RTD:	one_arg("@rtd(", e); break;
	case RND:	one_arg("@rnd(", e); break;
	case ROUND:	two_arg("@round(", e); break;
	case HOUR:	one_arg("@hour(", e); break;
	case MINUTE:	one_arg("@minute(", e); break;
	case SECOND:	one_arg("@second(", e); break;
	case MONTH:	one_arg("@month(", e); break;
	case DAY:	one_arg("@day(", e); break;
	case YEAR:	one_arg("@year(", e); break;
	case NOW:	for (s = "@now"; (line[linelim++] = *s++); );
			linelim--;
			break;
	case DATE:	if (e->e.o.right)
			    two_arg("@date(", e);
			else
			    one_arg("@date(", e);
			break;
	case FMT:	two_arg("@fmt(", e); break;
	case UPPER:	one_arg("@upper(", e); break;
	case LOWER:	one_arg("@lower(", e); break;
	case CAPITAL:	one_arg("@capital(", e); break;
	case DTS:	three_arg("@dts(", e); break;
	case TTS:	three_arg("@tts(", e); break;
	case STON:	one_arg("@ston(", e); break;
	case EQS:	two_arg("@eqs(", e); break;
	case LMAX:	list_arg("@max(", e); break;
	case LMIN: 	list_arg("@min(", e); break;
	case FV:	three_arg("@fv(", e); break;
	case PV:	three_arg("@pv(", e); break;
	case PMT:	three_arg("@pmt(", e); break;
	case NVAL:	two_arg("@nval(", e); break;
	case SVAL:	two_arg("@sval(", e); break;
	case EXT:	two_arg("@ext(", e); break;
	case SUBSTR:	three_arg("@substr(", e); break;
	case STINDEX:	index_arg("@stindex", e); break;
	case INDEX:	index_arg("@index", e); break;
	case LOOKUP:	index_arg("@lookup", e); break;
	case HLOOKUP:	two_arg_index("@hlookup", e); break;
	case VLOOKUP:	two_arg_index("@vlookup", e); break;
	case IF:	three_arg("@if(", e); break;
	case MYROW:	for (s = "@myrow"; (line[linelim++] = *s++); );
			linelim--;
			break;
	case MYCOL:	for (s = "@mycol"; (line[linelim++] = *s++); );
			linelim--;
			break;
	case LASTROW:	for (s = "@lastrow"; (line[linelim++] = *s++); );
			linelim--;
			break;
	case LASTCOL:	for (s = "@lastcol"; (line[linelim++] = *s++); );
			linelim--;
			break;
	case COLTOA:	one_arg( "@coltoa(", e); break;
	case FILENAME:	one_arg( "@filename(", e); break;
	case NUMITER:	for (s = "@numiter"; (line[linelim++] = *s++); );
                        linelim--;
                        break;
	case ERR_:	for (s = "@err"; (line[linelim++] = *s++); );
                        linelim--;
                        break;
	case PI_:	for (s = "@pi"; (line[linelim++] = *s++); );
                        linelim--;
                        break;
	case BLACK:	for (s = "@black"; (line[linelim++] = *s++); );
                        linelim--;
                        break;
	case RED:	for (s = "@red"; (line[linelim++] = *s++); );
                        linelim--;
                        break;
	case GREEN:	for (s = "@green"; (line[linelim++] = *s++); );
                        linelim--;
                        break;
	case YELLOW:	for (s = "@yellow"; (line[linelim++] = *s++); );
                        linelim--;
                        break;
	case BLUE:	for (s = "@blue"; (line[linelim++] = *s++); );
                        linelim--;
                        break;
	case MAGENTA:	for (s = "@magenta"; (line[linelim++] = *s++); );
                        linelim--;
                        break;
	case CYAN:	for (s = "@cyan"; (line[linelim++] = *s++); );
                        linelim--;
                        break;
	case WHITE:	for (s = "@white"; (line[linelim++] = *s++); );
                        linelim--;
                        break;
	default:	decompile(e->e.o.left, mypriority);
			line[linelim++] = e->op;
			decompile(e->e.o.right, mypriority+1);
			break;
	}
	if (mypriority<priority) line[linelim++] = ')';
    } else line[linelim++] = '?';
}

void
index_arg(char *s, struct enode *e)
{
    if (e->e.o.right && e->e.o.right->op == ',') {
	two_arg_index(s, e);
	return;
    }
    for (; (line[linelim++] = *s++); );
    linelim--;
    range_arg("(", e->e.o.left);
    linelim--;
    if (e->e.o.right) {
	line[linelim++] = ',';
	decompile(e->e.o.right, 0);
    }
    line[linelim++] = ')';
}

void
two_arg_index(char *s, struct enode *e)
{
    for (; (line[linelim++] = *s++); );
    linelim--;
    range_arg("(", e->e.o.left);
    linelim--;
    line[linelim++] = ',';
    decompile(e->e.o.right->e.o.left, 0);
    line[linelim++] = ',';
    decompile(e->e.o.right->e.o.right, 0);
    line[linelim++] = ')';
}

void
list_arg(char *s, struct enode *e)
{
    for (; (line[linelim++] = *s++); );
    linelim--;

    decompile(e->e.o.right, 0);
    line[linelim++] = ',';
    decompile_list(e->e.o.left);
    line[linelim - 1] = ')';
}

void
one_arg(char *s, struct enode *e)
{
    for (; (line[linelim++] = *s++); );
    linelim--;
    decompile(e->e.o.left, 0);
    line[linelim++] = ')';
}

void
two_arg(char *s, struct enode *e)
{
    for (; (line[linelim++] = *s++); );
    linelim--;
    decompile(e->e.o.left, 0);
    line[linelim++] = ',';
    decompile (e->e.o.right, 0);
    line[linelim++] = ')';
}

void
three_arg(char *s, struct enode *e)
{
    for (; (line[linelim++] = *s++); );
    linelim--;
    decompile(e->e.o.left, 0);
    line[linelim++] = ',';
    decompile(e->e.o.right->e.o.left, 0);
    line[linelim++] = ',';
    decompile (e->e.o.right->e.o.right, 0);
    line[linelim++] = ')';
}

void
range_arg(char *s, struct enode *e)
{
    struct range *r;

    for (; (line[linelim++] = *s++); );
    linelim--;
    if (!find_range((char *)0, 0, e->e.r.left.vp,
	    e->e.r.right.vp, &r) && r->r_is_range) {
	(void) sprintf(line+linelim, "%s", r->r_name);
	linelim += strlen(line+linelim);
    } else {
	decodev(e->e.r.left);
	line[linelim++] = ':';
	decodev(e->e.r.right);
    }
    line[linelim++] = ')';
}

void
editfmt(int row, int col)
{
    register struct ent *p;

    p = lookat(row, col);
    if (p->format) {
        (void) sprintf(line, "fmt %s \"%s\"", v_name(row, col), p->format);
	linelim = strlen(line);
    }
}

void
editv(int row, int col)
{
    register struct ent *p;

    p = lookat(row, col);
    (void) sprintf(line, "let %s = ", v_name(row, col));
    linelim = strlen(line);
    if (p->flags & is_valid) {
	if (p->flags & is_strexpr || p->expr == NULL) {
	    (void) sprintf(line+linelim, "%.15g", p->v);
	    linelim = strlen(line);
	} else
	    editexp(row, col);
    }
}

void
editexp(int row, int col)
{
    register struct ent *p;

    p = lookat(row, col);
    decompile(p->expr, 0);
    line[linelim] = '\0';
}

void
edits(int row, int col)
{
    register struct ent *p;

    p = lookat(row, col);
    if (p->flags & is_label)
	(void) sprintf(line, "label %s = ", v_name(row, col));
    else
	(void) sprintf(line, "%sstring %s = ",
		((p->flags & is_leftflush) ? "left" : "right"),
		v_name(row, col));
    linelim = strlen(line);
    if (p->flags & is_strexpr && p->expr) {
	editexp(row, col);
    } else if (p->label) {
        (void) sprintf(line+linelim, "\"%s\"", p->label);
        linelim += strlen(line+linelim);
    } else {
        (void) sprintf(line+linelim, "\"");
        linelim += 1;
    }
}

#ifdef RINT
/*	round-to-even, also known as ``banker's rounding''.
	With round-to-even, a number exactly halfway between two values is
	rounded to whichever is even; e.g. rnd(0.5)=0, rnd(1.5)=2,
	rnd(2.5)=2, rnd(3.5)=4.  This is the default rounding mode for
	IEEE floating point, for good reason: it has better numeric
	properties.  For example, if X+Y is an integer,
	then X+Y = rnd(X)+rnd(Y) with round-to-even,
	but not always with sc's rounding (which is
	round-to-positive-infinity).  I ran into this problem when trying to
	split interest in an account to two people fairly.
*/

double
rint(double d)
{
	/* as sent */
	double fl = floor(d),  fr = d-fl;
	return
	    fr<0.5  ||  fr==0.5 && fl==floor(fl/2)*2   ?   fl   :   ceil(d);
}
#endif
