[alt.sources.amiga] sc Part 3 of 9

sie@fulcrum.bt.co.uk (Simon Raybould) (03/20/91)

#!/bin/sh
# this is scshr.03 (part 3 of a multipart archive)
# do not concatenate these parts, unpack them in order with /bin/sh
# file gram.y continued
#
if test ! -r _shar_seq_.tmp; then
	echo 'Please unpack part 1 first!'
	exit 1
fi
(read Scheck
 if test "$Scheck" != 3; then
	echo Please unpack part "$Scheck" next!
	exit 1
 else
	exit 0
 fi
) < _shar_seq_.tmp || exit 1
if test ! -f _shar_wnt_.tmp; then
	echo 'x - still skipping gram.y'
else
echo 'x - continuing file gram.y'
sed 's/^X//' << 'SHAR_EOF' >> 'gram.y' &&
X	|	e '#' e		{ $$ = new ('#', $1, $3); }
X	;
X
expr_list:	e		{ $$ = new(ELIST, ENULL, $1); }
X	|	expr_list ',' e	{ $$ = new(ELIST, $1, $3); }
X	;
X
range:		var ':' var	{ $$.left = $1; $$.right = $3; }
X	| 	RANGE		{ $$ = $1; }
X	;
X
var:		COL NUMBER	{ $$.vp = lookat($2 , $1); $$.vf = 0;}
X	|	'$' COL NUMBER	{ $$.vp = lookat($3 , $2);
X					$$.vf = FIX_COL;}
X	|	COL '$' NUMBER	{ $$.vp = lookat($3 , $1);
X					$$.vf = FIX_ROW;}
X	|	'$' COL '$' NUMBER { $$.vp = lookat($4 , $2);
X					$$.vf = FIX_ROW | FIX_COL;}
X	|	VAR		{ $$ = $1.left; }
X	;
X
var_or_range:	range		{ $$ = $1; }
X	|	var		{ $$.left = $1; $$.right = $1; }
X	;
X
num:		NUMBER		{ $$ = (double) $1; }
X	|	FNUMBER		{ $$ = $1; }
X	|	'-' num		{ $$ = -$2; }
X	|	'+' num		{ $$ = $2; }
X	;
X
strarg:		STRING		{ $$ = $1; }
X	|	var		{
X				    char *s, *s1;
X				    s1 = $1.vp->label;
X				    if (!s1)
X					s1 = "NULL_STRING";
X				    s = xmalloc((unsigned)strlen(s1)+1);
X				    (void) strcpy(s, s1);
X				    $$ = s;
X				}
X  	;
X
setlist :	
X	|	setlist setitem
X	;
X
setitem	:	K_AUTO		{ setauto(1); }
X	|	K_AUTOCALC	{ setauto(1); }
X	|	'~' K_AUTO	{ setauto(0); }
X	|	'~' K_AUTOCALC	{ setauto(0); }
X	|	'!' K_AUTO	{ setauto(0); }
X	|	'!' K_AUTOCALC	{ setauto(0); }
X	|	K_BYCOLS	{ setorder(BYCOLS); }
X	|	K_BYROWS	{ setorder(BYROWS); }
X	|	K_BYGRAPH	{ setorder(BYGRAPH); }
X	|	K_NUMERIC	{ numeric = 1; }
X	|	'!' K_NUMERIC	{ numeric = 0; }
X	|	K_PRESCALE	{ prescale = 0.01; }
X	|	'!' K_PRESCALE	{ prescale = 1.0; }
X	|	K_EXTFUN	{ extfunc = 1; }
X	|	'!' K_EXTFUN	{ extfunc = 0; }
X	|	K_CELLCUR	{ showcell = 1; }
X	|	'!' K_CELLCUR	{ showcell = 0; }
X	|	K_TOPROW	{ showtop = 1; }
X	|	'!' K_TOPROW	{ showtop = 0; }
X	|	K_ITERATIONS '=' NUMBER	{ setiterations($3); }
X	|	K_TBLSTYLE '=' NUMBER	{ tbl_style = $3; }
X	|	K_TBLSTYLE '=' K_TBL	{ tbl_style = TBL; }
X	|	K_TBLSTYLE '=' K_LATEX	{ tbl_style = LATEX; }
X	|	K_TBLSTYLE '=' K_TEX	{ tbl_style = TEX; }
X  	;
SHAR_EOF
echo 'File gram.y is complete' &&
chmod 0666 gram.y ||
echo 'restore of gram.y failed'
Wc_c="`wc -c < 'gram.y'`"
test 12082 -eq "$Wc_c" ||
	echo 'gram.y: original size 12082, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= help.c ==============
if test -f 'help.c' -a X"$1" != X"-c"; then
	echo 'x - skipping help.c (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting help.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'help.c' &&
/*
X * Help functions for sc 
X * R. Bond, 1988
X * $Revision: 6.8 $
X */
X
#include <curses.h>
#include "sc.h"
X
char *intro[] = {
" ",
" Overview:",
" ",
" A:   This overview",
" B:   Options",
" C:   Cursor movement commands",
" D:   Cell entry and editing commands",
" E:   Line Editing",
" F:   File commands",
" G:   Row and column commands",
" H:   Range commands",
" I:   Miscellaneous commands",
" J:   Variable names/Expressions",
" K:   Range functions",
" L:   Numeric functions",
" M:   String functions",
" N:   Financial functions",
" O:   Time and date functions",
" ",
" Q:   Return to main spreadsheet",
(char *)0
};
X
char *options[] = {
" ",
" B: Options",
" ",
"     ^To  Toggle options. Toggle one option selected by o:",
" ",
"          a    Recalculate automatically or on ``@'' commands.",
"          c    Current cell highlighting enable/disable.",  
"          e    External function execution enable/disable.",
"          n    If enabled, a digit starts a numeric value.",
"          t    Top line display enable/disable.",
"          x    Encrypt/decrypt database and listing files.",
"          $    Dollar prescale.  If enabled, all numeric constants.",
"               (not expressions) entered are multipled by 0.01.",
" ",
"     S    Set options.  Options include:",
" ",
"          byrows        Recalculate in row order. (default)",
"          bycols        Recalculate in column order.",
"          iterations=n  Set the number of iterations allowed. (10)",
"          tblstyle=xx   Set ``T'' output style to:",
"                        0 (none), tex, latex, or tbl.",
(char *)0
};
X
char *cursor[] = {
" ",
" C: Cell cursor movement (always OK):",
" ",
"     ^N ^P ^B ^F Down, up, back, forward",
"     ^Ed         Go to end of range.  Follow ^E by a direction indicator",
"                 such as ^P or j.",
"     Arrow keys (if the terminal and termcap support them.)",
" ",
" Cell cursor movement if no prompt active:",
"     j,k,l,h    Down, up, right, left",
"     SPACE      Forward",
"     ^H         Back",
"     TAB        Forward, otherwise starts/ends a range",
"     ^          Up to row 0 of the current column.",
"     #          Down to the last valid row of the current column.",
"     0          Back to column A.  Preface with ^U if numeric mode.",
"     $          Forward to the last valid column of the current row.",
"     b          Back then up to the previous valid cell.",
"     w          Forward then down to the next valid cell.",
"     g          Go to a cell.  Cell name, range name, quoted string,",
"                or a number specify which cell.",
(char *)0
};
X
X
char *cell[] = {
" ",
" D: Cell entry and editing commands:",
" ",
"     =    Enter a numeric constant or expression.",
"     <    Enter a left justified string or string expression.",
"     \",>  Enter a right justified string or string expression.",
"     e    Edit the current cell's numeric value.",
"     E    Edit the current cell's string part.",
"     x    Clear the current cell.",
"     c    Copy the last marked cell to the current cell.",
"     m    Mark a cell to be used as the source for ``c''",
"     +    Increment numeric part",
"     -    Decrement numeric part",
" ",
"     In numeric mode, a decimal digit, ``+'', ``-'', and ``.'' all start",
"     a new numeric constant or expression.",
(char *)0
};
X
X
char *vi[] = {
" ",
" E: Line Editor",
" ",
"     Hitting the ESC key while entering any command on the top line",
"     will start a one-line vi-style editor.  Supported commands:",
" ",
"     ESC q        Abort command entry.",
"     h l          Move cursor forward, backward.",
"     0 $          Move cursor to the beginning, end of the line.",
"     b w          Move cursor forward/back one word.",
"     fc           Move cursor to character c.",
"     tc           Move the cursor the the character before c.",
"     i a          Enter insert mode before/after the cursor.",
"     I            Move to cursor column 0 and enter insert mode.",
"     x X          Delete the character under/before the cursor.",
"     rc           Replace the character under the cursor with c.",
"     cm           Change - m = b,f,h,l,t or w.",
"     dm           Delete - m = b,f,h,l,t or w.",
"     R            Enter replace (overstrike) mode.",
"     + j - k /    Forward/backward/search the command history.",
"     n            Repeat last history search.",
"     . u          Repeat/undo the last command.",
(char *)0
};
X
char *file[] = {
" ",
" F: File commands:",
" ",
"     G    Get a new database from a file. ",
"     M    Merge a new file into the current database.",
"     P    Put the current database into a file.",
"     W    Write a listing of the current database into a file in",
"          a form that matches its appearance on the screen.",
"     T    Write a listing of the current database to a file, but",
"          put delimiters between each pair of fields.",
"          Optionally brackets output with control lines for ``tbl'',",
"          ``LaTeX'', or ``TeX''.",
" ",
"     If encryption mode is set, file I/O will be encrypted/decrypted.",
"     ``\"| program\"'' for a file name will pipe (unencrypted) output to",
"     a program for Put, Write and Table.  If a cell name is used",
"     as the file name, the cell's string part will be used as the",
"     file name.",
(char *)0
};
X
X
char *row[] = {
" ",
" G: Row and column commands:",
" ",
"     ir, ic      Insert a new, empty row (column)",
"     ar, ac      Append a new copy of the current row (column)",
"     dr, dc      Delete the current row (column)",
"     pr, pc, pm  Pull deleted cells back into the spreadsheet",
"                 Insert rows, columns or merge the cells.",
"     vr, vc      Remove expressions from the affected rows (columns),",
"                 leaving only the values.",
"     zr, zc      Hide (``zap'') the current row (column)",
"     sr, sc      Show hidden rows (columns)",
"     f           Set the output format to be used with the values of",
"                 each cell in this column.  Enter field width and",
"                 number of fractional digits.  A preceding count can be",
"                 used to change more than one column.",
" ",
"     Commands which move or copy cells also modify the row and column ",
"     references in the new cell expressions.  Use ``fixed'' or the",
"     ``$'' style cell reference to supress the change.",
(char *)0
};
X
X
char *range[] = {
" ",
" H: Range commands:",
" ",
"     /x   Clear a range. ",
"     /v   Remove the expressions from a range of cells, leaving ",
"          just the values.",
"     /c   Copy a source range to a destination range.",
"     /f   Fill a range with constant values starting with a given",
"          value and increasing by a given increment.",
"     /d   Assign a name to a cell or a range of cells.  Give the",
"          the name, surrounded by quotes, and either a cell name such",
"          as ``A10'' or a range such as ``a1:b20''.",
"     /s   Shows the currently defined range names.  Pipe output to",
"          sort, then to less.",
"     /u   Use this command to undefine a previously defined range",
"          name.",
" ",
"     Range operations affect a rectangular region on the screen",
"     defined by the upper left and lower right cells in the region.",
"     A range is specified by giving the cell names separated by ``:'',",
"     such as ``a20:k52''.  Another way to refer to a range is to use",
"     a name previously defined using ``/d''.",
(char *)0
};
X
X
char *misc[] = {
" ",
" I: Miscellaneous commands:",
" ",
"     Q q ^C   Exit from the program.",
"     ^G ESC   Abort entry of the current command.",
"     ?        Help",
"     !        Shell escape.  Enter a command to run.  ``!!'' repeats",
"              the last command.  Just ``!'' starts an interactive shell.",
"     ^L       Redraw the screen.",
"     ^R       Redraw the screen.  Highlight cells with values but no",
"              expressions.",
"     ^X       Redraw the screen.  Show formulas, not values.",
"     @        Recalculate the spreadsheet.",
"     ^V       Type, in the command line, the name of the current cell.",
"     ^W       Type, in the command line, the current cell's expression.",
"     ^A       Type, in the command line, the current cell's numeric value.",
"     TAB      When the character cursor is on the top line TAB can be used",
"              to start or stop the display of the default range.",
(char *)0
};
X
char *var[] = {
" ",
" J: Variable names:",
" ",
"     K20    Row and column can vary on copies.",
"     $K$20  Row and column stay fixed on copies.",
"     $K20   Row can vary; column stays fixed on copies.",
"     K$20   Row stays fixed; column can vary on copies.",
"     fixed  holds following expession fixed on copies.",
"     Cells and ranges can be given a symbolic name via ``/d''.",
" ",
" Expressions:",
"     -e      Negation                e<=e  Less than or equal",
"     e+e     Addition                e=e   Equal",
"     e-e     Subtraction             e!=e  Not Equal",
"     e*e     Multiplication          e>=e  Greater than or equal",
"     e/e     Division                e>e  Greater than",
"     e%e     Modulo                  e<e  Less than",
"     e^e     Exponentiation          e&e  Boolean operator AND.",
"     ~e      Boolean operator NOT    e|e  Boolean operator OR",
"     e?e1:e2  or @if(e,e1,e2)",
"             Conditional: If e is non zero then then e1, else e2.",
"     Terms may be constants, variables, and parenthesized expressions.",
(char *)0
};
X
char *rangef[] = {
" ",
" K: Range functions:",
" ",
"     @sum(r)           Sum all valid cells in the range.",
"     @prod(r)          Multiply together all valid cells in the range.",
"     @avg(r)           Average all valid cells in the range.",
"     @count(r)         Count all valid cells in the range.",
"     @max(r)           Return the maximum value in the range.",
"     @min(r)           Return the minimum value in the range.",
"     @stddev(r)        Return the sample standard deviation of ",
"                       the cells in the range.",
"     @index(e,r) @stindex(e,r)",
"                       Return the numeric (string) value of the cell at",
"                       index e into range r.",
"     @lookup(e,r) @hlookup(e,r,n) @vlookup(e,r,n)",
"                       Search through the range r for a value that",
"                       matches e.  If e is numeric, the last value <= e",
"                       matches; if string, an exact match is required.",
"                       @lookup searches a single row (column) and returns",
"                       the value from the next column (row); @hlookup",
"                       (@vlookup) searches the first row (column) in r and",
"                       returns the value n columns (rows) from the match.",
(char *)0
};
X
char *numericf[] = {
" ",
" L: Numeric functions:",
" ",
"     @atan2(e1,e2)     Arc tangent of e1/e2.",
"     @ceil(e)          Smallest integer not less than e.",
"     @eqs(se1,se2)     1 if string expr se1 has the same value as se2.",
"     @exp(e)           Exponential function of e.",
"     @abs(e) @fabs(e)  Absolute value of e.",
"     @floor(e)         The largest integer not greater than e.",
"     @hypot(x,y)       Sqrt(x*x+y*y).",
"     @max(e1,e2,...)   The maximum of the values of the e's.",
"     @min(e1,e2,...)   The minimum of the values of the e's",
"     @nval(se,e)       The numeric value of a named cell.",
"     pi                A constant quite close to pi.",
"     @pow(e1,e2)       e1 raised to the power of e2.",
"     @rnd(e)           Round e to the nearest integer.",
"     @round(e,n)       Round e to n decimal places.",
"     @sqrt(e)          Square root of e.",
"     @ston(se)         Convert string expr se to a numeric",
"     @ln(e)   @log(e)           Natural/base 10 logarithm of e.",
"     @dtr(e)  @rtd(e)           Convert degrees to/from radians.",
"     @cos(e)  @sin(e)  @tan(e)  Trig functions of radian arguments.",
"     @asin(e) @acos(e) @atan(e) Inverse trig function.",
(char *)0
};
X
char *stringf[] = {
" ",
" M: String functions:",
" ",
"     #                 Concatenate strings.  For example, the",
"                       string expression ``A0 # \"zy dog\"'' yields",
"                       ``the lazy dog'' if A0 is ``the la''.",
"     @substr(se,e1,e2) Extract characters e1 through e2 from the",
"                       string expression se.  For example,",
"                       ``@substr(\"Nice jacket\" 4, 7)'' yields ",
"                       ``e jac''.",
"     @fmt(se,e)        Convert a number to a string using sprintf(3).",
"                       For example,  ``@fmt(\"*%6.3f*\",10.5)'' yields",
"                       ``*10.500*''.  Use formats are e, E, f, g, and G.", 
"     @sval(se,e)       Return the string value of a cell selected by name.",
"     @ext(se,e)        Call an external function (program or",
"                       script).  Convert e to a string and append it",
"                       to the command line as an argument.  @ext yields",
"                       a string: the first line printed to standard",
"                       output by the command.",
"     String expressions are made up of constant strings (characters",
"     surrounded by quotes), variables, and string functions.",
(char *)0
};
X
X
char *finf[] = {
" ",
" N: Financial functions:",
" ",
"     @pmt(e1,e2,e3)    @pmt(60000,.01,360) computes the monthly",
"                       payments for a $60000 mortgage at 12%",
"                       annual interest (.01 per month) for 30",
"                       years (360 months).",
" ",
"     @fv(e1,e2,e3)     @fv(100,.005,36) computes the future value",
"                       of 36 monthly payments of $100 at 6%",
"                       interest (.005 per month).  It answers the",
"                       question:  ``How much will I have in 36",
"                       months if I deposit $100 per month in a",
"                       savings account paying 6% interest com-",
"                       pounded monthly?''",
" ",
"     @pv(e1,e2,e3)     @pv(1000,.015,36) computes the present",
"                       value of an ordinary annuity of 36",
"                       monthly payments of $1000 at 18% annual",
"                       interest.  It answers the question: ``How",
"                       much can I borrow at 18% for 30 years if I",
"                       pay $1000 per month?''",
(char *)0
};
X
X
char *timef[] = {
" ",
" O: Time and date functions:",
" ",
"     @now              Return the time encoded in seconds since 1970.",
"     @dts(m,d,y)       Return m/d/y encoded in seconds since 1970.",
"     @tts(h,m,s)       Return h:m:s encoded in seconds since midnight.",
" ",
"     All of the following take an argument expressed in seconds:",
" ",
"     @date(e)          Convert the time in seconds to a date",
"                       string 24 characters long in the following",
"                       form: ``Sun Sep 16 01:03:52 1973''.  Note",
"                       that you can extract pieces of this fixed format",
"                       string with @substr.",
"     @year(e)          Return the year.  Valid years begin with 1970.",
"     @month(e)         Return the month: 1 (Jan) to 12 (Dec).",
"     @day(e)           Return the day of the month: 1 to 31.",
"     @hour(e)          Return the number of hours since midnight: 0 to 23.",
"     @minute(e)        Return the number of minutes since the",
"                       last full hour: 0 to 59.",
"     @second(e)        Return the number of seconds since the",
"                       last full minute: 0 to 59.",
(char *)0
};
void
help()
{
X    int option;
X    char **ns = intro;
X
X    while((option = pscreen(ns)) != 'q' && option != 'Q') {
X    	switch (option) {
X	case 'a': case 'A': ns = intro; break;
X	case 'b': case 'B': ns = options; break;
X	case 'c': case 'C': ns = cursor; break;
X	case 'd': case 'D': ns = cell; break;
X	case 'e': case 'E': ns = vi; break;
X	case 'f': case 'F': ns = file; break;
X	case 'g': case 'G': ns = row; break;
X	case 'h': case 'H': ns = range; break;
X	case 'i': case 'I': ns = misc; break;
X	case 'j': case 'J': ns = var; break;
X	case 'k': case 'K': ns = rangef; break;
X	case 'l': case 'L': ns = numericf; break;
X	case 'm': case 'M': ns = stringf; break;
X	case 'n': case 'N': ns = finf; break;
X	case 'o': case 'O': ns = timef; break;
X	default: ns = intro; break;
X	}
X    }
X    FullUpdate++;
X    (void) move(1,0);
X    (void) clrtobot();
}
X
pscreen(screen)
char *screen[];
{
X    int line;
X    int dbline;
X
X    (void) move(1,0);
X    (void) clrtobot();
X    dbline = 1;
X    for (line = 0; screen[line]; line++) {
X	(void) move(dbline++, 4);
X	(void) addstr (screen[line]);
X	(void) clrtoeol();
X    }
X    (void) move(0,0);
X    (void) printw("Which Screen? [a-n, q]");
X    (void) clrtoeol();
X    (void) refresh();
X    return(nmgetch());
}
SHAR_EOF
chmod 0666 help.c ||
echo 'restore of help.c failed'
Wc_c="`wc -c < 'help.c'`"
test 16990 -eq "$Wc_c" ||
	echo 'help.c: original size 16990, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= interp.c ==============
if test -f 'interp.c' -a X"$1" != X"-c"; then
	echo 'x - skipping interp.c (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting interp.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'interp.c' &&
/*	SC	A Spreadsheet Calculator
X *		Expression interpreter and assorted support routines.
X *
X *		original by James Gosling, September 1982
X *		modified by Mark Weiser and Bruce Israel, 
X *			University of Maryland
X *
X *              More mods Robert Bond, 12/86
X *		More mods by Alan Silverstein, 3-4/88, see list of changes.
X *		$Revision: 6.8 $
X */
X
#define DEBUGDTS 1		/* REMOVE ME */
/* #define EXPRTREE	/* expr. dependency tree stuff, not ready yet */
X
#ifdef aiws
#undef _C_func			/* Fixes for undefined symbols on AIX */
#endif
X
#ifdef IEEE_MATH
#include <ieeefp.h>
#endif /* IEEE_MATH */
X
#include <math.h>
#ifdef AMIGA
double hypot();
#endif /* AMIGA */
#include <signal.h>
#include <setjmp.h>
#include <stdio.h>
X
extern int errno;		/* set by math functions */
#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
X
#include <curses.h>
#include "sc.h"
X
#if defined(BSD42) || defined(BSD43)
char *re_comp();
#endif
#if defined(SYSV2) || defined(SYSV3)
char *regcmp();
char *regex();
#endif
X
#ifdef SIGVOID
X    void quit();
#else
X    int quit();
#endif
X
/* Use this structure to save the the last 'g' command */
X
struct go_save {
X	int g_type;
X	double g_n;
X	char *g_s;
X	int  g_row;
X	int  g_col;
} gs;
X
/* g_type can be: */
X
#define G_NONE 0			/* Starting value - must be 0*/
#define G_NUM 1
#define G_STR 2
#define G_CELL 3
X
#define ISVALID(r,c)	((r)>=0 && (r)<maxrows && (c)>=0 && (c)<maxcols)
X
extern FILE *popen();
X
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 */
double fn1_eval();
double fn2_eval();
struct	ent *firstev = (struct ent *)0;	/* first expr in the eval list */
X
#define PI (double)3.14159265358979323846
#define dtr(x) ((x)*(PI/(double)180.0))
#define rtd(x) ((x)*(180.0/(double)PI))
X
double finfunc(fun,v1,v2,v3)
int fun;
double v1,v2,v3;
{
X 	double answer,p;
X 
X 	p = fn2_eval(pow, 1 + v2, v3);
X 
X 	switch(fun)
X 	{
X 	case PV:
X 		answer = v1 * (1 - 1/p) / v2;
X 		break;
X 	case FV:
X 		answer = v1 * (p - 1) / v2;
X 		break;
X 	case PMT:
X 		answer = v1 * v2 / (1 - 1/p);
X 		break;
X	default:
X		error("Unknown function in finfunc");
X		return((double)0);
X	}
X	return(answer);
}
X
char *
dostindex( val, minr, minc, maxr, maxc)
double val;
int minr, minc, maxr, maxc;
{
X    register r,c;
X    register struct ent *p;
X    char *pr;
X    int x;
X
X    x = (int) val;
X    r = minr; c = minc;
X    p = (struct ent *)0;
X    if ( minr == maxr ) { /* look along the row */
X	c = minc + x - 1;
X	if (c <= maxc && c >=minc)
X	    p = *ATBL(tbl, r, c);
X    } else if ( minc == maxc ) { /* look down the column */
X	r = minr + x - 1;
X	if (r <= maxr && r >=minr)
X	    p = *ATBL(tbl, r, c);
X    } else {
X	error ("range specified to @stindex");
X	return((char *)0);
X    }
X
X    if (p && p->label) {
X	pr = xmalloc((unsigned)(strlen(p->label)+1));
X	(void)strcpy(pr, p->label);
X	return (pr);
X     } else
X	return((char *)0);
}
X
double
doindex( val, minr, minc, maxr, maxc)
double val;
int minr, minc, maxr, maxc;
{
X    double v;
X    register r,c;
X    register struct ent *p;
X    int x;
X
X    x = (int) val;
X    v = (double)0;
X    r = minr; c = minc;
X    if ( minr == maxr ) { /* look along the row */
X	c = minc + x - 1;
X	if (c <= maxc && c >=minc 
X		&& (p = *ATBL(tbl, r, c)) && p->flags&is_valid )
X					return p->v;
X	}
X    else if ( minc == maxc ){ /* look down the column */
X	r = minr + x - 1;
X	if (r <= maxr && r >=minr 
X		&& (p = *ATBL(tbl, r, c)) && p->flags&is_valid )
X					return p->v;
X	}
X    else error(" range specified to @index");
X    return v;
}
X
double
dolookup( val, minr, minc, maxr, maxc, offr, offc)
struct enode * val;
int minr, minc, maxr, maxc, offr, offc;
{
X    double v, ret = (double)0;
X    register r,c;
X    register struct ent *p = (struct ent *)0;
X    int incr,incc,fndr,fndc;
X    char *s;
X
X    incr = (offc != 0); incc = (offr != 0);
X    if (etype(val) == NUM) {
X	v = eval(val);
X	for (r = minr, c = minc; r <= maxr && c <= maxc; r+=incr, c+=incc) {
X	    if ( (p = *ATBL(tbl, r, c)) && p->flags&is_valid ) {
X		if (p->v <= v) {
X		    fndr = incc ? (minr + offr) : r;
X		    fndc = incr ? (minc + offc) : c;
X		    if (ISVALID(fndr,fndc))
X			p = *ATBL(tbl, fndr, fndc);
X		    else error(" range specified to @[hv]lookup");
X		    if ( p && p->flags&is_valid)
X			ret = p->v;
X		} else break;
X	    }
X	}
X    } else {
X	s = seval(val);
X	for (r = minr, c = minc; r <= maxr && c <= maxc; r+=incr, c+=incc) {
X	    if ( (p = *ATBL(tbl, r, c)) && p->label ) {
X		if (strcmp(p->label,s) == 0) {
X		    fndr = incc ? (minr + offr) : r;
X		    fndc = incr ? (minc + offc) : c;
X		    if (ISVALID(fndr,fndc))
X			p = *ATBL(tbl, fndr, fndc);
X		    else error(" range specified to @[hv]lookup");
X		    break;
X		}
X	    }
X	}
X	if ( p && p->flags&is_valid)
X	    ret = p->v;
X	xfree(s);
X    }
X    return ret;
}
X
double
docount(minr, minc, maxr, maxc)
int minr, minc, maxr, maxc;
{
X    int v;
X    register r,c;
X    register struct ent *p;
X
X    v = 0;
X    for (r = minr; r<=maxr; r++)
X	for (c = minc; c<=maxc; c++)
X	    if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid)
X		v++;
X    return v;
}
X
double
dosum(minr, minc, maxr, maxc)
int minr, minc, maxr, maxc;
{
X    double v;
X    register r,c;
X    register struct ent *p;
X
X    v = (double)0;
X    for (r = minr; r<=maxr; r++)
X	for (c = minc; c<=maxc; c++)
X	    if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid)
X		v += p->v;
X    return v;
}
X
double
doprod(minr, minc, maxr, maxc)
int minr, minc, maxr, maxc;
{
X    double v;
X    register r,c;
X    register struct ent *p;
X
X    v = 1;
X    for (r = minr; r<=maxr; r++)
X	for (c = minc; c<=maxc; c++)
X	    if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid)
X		v *= p->v;
X    return v;
}
X
double
doavg(minr, minc, maxr, maxc)
int minr, minc, maxr, maxc;
{
X    double v;
X    register r,c,count;
X    register struct ent *p;
X
X    v = (double)0;
X    count = 0;
X    for (r = minr; r<=maxr; r++)
X	for (c = minc; c<=maxc; c++)
X	    if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid) {
X		v += p->v;
X		count++;
X	    }
X
X    if (count == 0) 
X	return ((double) 0);
X
X    return (v / (double)count);
}
X
double
dostddev(minr, minc, maxr, maxc)
int minr, minc, maxr, maxc;
{
X    double lp, rp, v, nd;
X    register r,c,n;
X    register struct ent *p;
X
X    n = 0;
X    lp = 0;
X    rp = 0;
X    for (r = minr; r<=maxr; r++)
X	for (c = minc; c<=maxc; c++)
X	    if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid) {
X		v = p->v;
X		lp += v*v;
X		rp += v;
X		n++;
X	    }
X
X    if ((n == 0) || (n == 1)) 
X	return ((double) 0);
X    nd = (double)n;
X    return (sqrt((nd*lp-rp*rp)/(nd*(nd-1))));
}
X
double
domax(minr, minc, maxr, maxc)
int minr, minc, maxr, maxc;
{
X    double v = (double)0;
X    register r,c,count;
X    register struct ent *p;
X
X    count = 0;
X    for (r = minr; r<=maxr; r++)
X	for (c = minc; c<=maxc; c++)
X	    if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid) {
X		if (!count) {
X		    v = p->v;
X		    count++;
X		} else if (p->v > v)
X		    v = p->v;
X	    }
X
X    if (count == 0) 
X	return ((double) 0);
X
X    return (v);
}
X
double
domin(minr, minc, maxr, maxc)
int minr, minc, maxr, maxc;
{
X    double v = (double)0;
X    register r,c,count;
X    register struct ent *p;
X
X    count = 0;
X    for (r = minr; r<=maxr; r++)
X	for (c = minc; c<=maxc; c++)
X	    if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid) {
X		if (!count) {
X		    v = p->v;
X		    count++;
X		} else if (p->v < v)
X		    v = p->v;
X	    }
X
X    if (count == 0) 
X	return ((double) 0);
X
X    return (v);
}
X
#define sec_min 60
#define sec_hr  3600L
#define sec_day 86400L
#define sec_yr  31471200L     /* 364.25 days/yr */
#define sec_mo  2622600L       /* sec_yr/12: sort of an average */
int mdays[12]={ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
X
double
dodts(mo, day, yr)
int mo, day, yr;
{
X    long trial;
X    register struct tm *tp; 
X    register int i;
X    register long jdate;
X
X    mdays[1] = 28 + (yr%4 == 0);
X
X    if (mo < 1 || mo > 12 || day < 1 || day > mdays[--mo] ||
X		yr > 1999 || yr < 1970) {
X	error("@dts: invalid argument");
X	return(0.0);
X    }
X
X    jdate = day-1;
X    for (i=0; i<mo; i++)
X	    jdate += mdays[i];
X    for (i = 1970; i < yr; i++)
X	    jdate += 365 + (i%4 == 0);
X
X    trial = jdate * sec_day; 
X
X    yr -= 1900;
X
X    tp = localtime(&trial);
X
X    if (tp->tm_year != yr) {
X	    /*
X	    * We may fail this test once a year because of time zone
X	     * and daylight savings time errors.  This bounces the
X	     * trial time past the boundary.  The error introduced is
X	     * corrected below.
X	     */
X	    trial += sec_day*(yr - tp->tm_year);
X	    tp = localtime(&trial);
X    }
X    if (tp->tm_mon != mo) {
X	    /* We may fail this test once a month.  */
X	    trial += sec_day*(mo - tp->tm_mon);
X	    tp = localtime(&trial);
X    }
X    if (tp->tm_mday + tp->tm_hour + tp->tm_min + tp->tm_sec != day) {
X	trial -= (tp->tm_mday - day)*sec_day +  tp->tm_hour*sec_hr
X		 + tp->tm_min*sec_min + tp->tm_sec;
X    }
X
#ifdef DEBUGDTS
X    tp = localtime(&trial);
X    if (tp->tm_mday + tp->tm_hour + tp->tm_min + tp->tm_sec + 
X	tp->tm_year + tp->tm_mon != yr+mo+day)
X		error("Dts broke down");
#endif
X
X    return ((double)trial);
}
X
double
dotts(hr, min, sec)
int hr, min, sec;
{
X    if (hr < 0 || hr > 23 || min < 0 || min > 59 || sec < 0 || sec > 59) {
X	error ("@tts: Invalid argument");
X	return ((double)0);
X    }
X    return ((double)(sec+min*60+hr*3600));
}
X
double
dotime(which, when)
int which;
double when;
{
X	long time();
X
X	static long t_cache;
X	static struct tm tm_cache;
X	struct tm *tp;
X	long tloc;
X
X	if (which == NOW) 
X	    return (double)time((long *)0);
X
X	tloc = (long)when;
X
X	if (tloc != t_cache) {
X	    tp = localtime(&tloc);
X	    tm_cache = *tp;
X	    tm_cache.tm_mon += 1;
X	    tm_cache.tm_year += 1900;
X	    t_cache = tloc;
X	}
X
X	switch (which) {
X	    case HOUR: return((double)(tm_cache.tm_hour));
X	    case MINUTE: return((double)(tm_cache.tm_min));
X	    case SECOND: return((double)(tm_cache.tm_sec));
X	    case MONTH: return((double)(tm_cache.tm_mon));
X	    case DAY: return((double)(tm_cache.tm_mday));
X	    case YEAR: return((double)(tm_cache.tm_year));
X	}
X	/* Safety net */
X	return ((double)0);
}
X
double
doston(s)
char *s;
{
X    char *strtof();
X    double v;
X
X    if (!s)
X	return((double)0);
X
X    (void)strtof(s, &v);
X    xfree(s);
X    return(v);
}
X
double
doeqs(s1, s2)
char *s1, *s2;
{
X    double v;
X
X    if (!s1 && !s2)
X	return((double)1.0);
X
X    if (!s1 || !s2)
X	v = 0.0;
X    else if (strcmp(s1, s2) == 0)
X	v = 1.0;
X    else
X	v = 0.0;
X
X    if (s1)
X    	xfree(s1);
X
X    if (s2)
X    	xfree(s2);
X
X    return(v);
}
X
X
/*
X * Given a string representing a column name and a value which is a column
X * number, return a pointer to the selected cell's entry, if any, else 0.  Use
X * only the integer part of the column number.  Always free the string.
X */
X
struct ent *
getent (colstr, rowdoub)
X    char *colstr;
X    double rowdoub;
{
X    int collen;		/* length of string */
X    int row, col;	/* integer values   */
X    struct ent *ep = (struct ent *)0;	/* selected entry   */
X
X    if (((row = (int) floor (rowdoub)) >= 0)
X     && (row < maxrows)				/* in range */
X     && ((collen = strlen (colstr)) <= 2)	/* not too long */
X     && ((col = atocol (colstr, collen)) >= 0)
X     && (col < maxcols))			/* in range */
X    {
X	ep = *ATBL(tbl, row, col);
X    }
X
X    xfree (colstr);
X    return (ep);
}
X
X
/*
X * Given a string representing a column name and a value which is a column
X * number, return the selected cell's numeric value, if any.
X */
X
double
donval (colstr, rowdoub)
X    char *colstr;
X    double rowdoub;
{
X    struct ent *ep;
X
X    return (((ep = getent (colstr, rowdoub)) && ((ep -> flags) & is_valid)) ?
X	    (ep -> v) : (double)0);
}
X
X
/*
X *	The list routines (e.g. dolmax) are called with an LMAX enode.
X *	The left pointer is a chain of ELIST nodes, the right pointer
X *	is a value.
X */
double
dolmax(ep)
struct enode *ep;
{
X	register int count = 0;
X	register double maxval = 0; /* Assignment to shut up lint */
X	register struct enode *p;
X	register double v;
X
X	for (p = ep; p; p = p->e.o.left) {
X		v = eval(p->e.o.right);
X		if (!count || v > maxval) {
X			maxval = v; count++;
X		}
X	}
X	if (count) return maxval;
X	else return (double)0;
}
X
double
dolmin(ep)
struct enode *ep;
{
X	register int count = 0;
X	register double minval = 0; /* Assignment to shut up lint */
X	register struct enode *p;
X	register double v;
X
X	for (p = ep; p; p = p->e.o.left) {
X		v = eval(p->e.o.right);
X		if (!count || v < minval) {
X			minval = v; count++;
X		}
X	}
X	if (count) return minval;
X	else return (double)0;
}
X
double 
eval(e)
register struct enode *e;
{
X    if (e == (struct enode *)0) return (double)0;
X    switch (e->op) {
X	case '+':	return (eval(e->e.o.left) + eval(e->e.o.right));
X	case '-':	return (eval(e->e.o.left) - eval(e->e.o.right));
X	case '*':	return (eval(e->e.o.left) * eval(e->e.o.right));
X	case '/':       return (eval(e->e.o.left) / eval(e->e.o.right));
X	case '%':     {	double num, denom;
X			num = floor(eval(e->e.o.left));
X			denom = floor(eval (e->e.o.right));
X			return denom ? num - floor(num/denom)*denom : (double)0; }
X	case '^':	return (fn2_eval(pow,eval(e->e.o.left),eval(e->e.o.right)));
X	case '<':	return (eval(e->e.o.left) < eval(e->e.o.right));
X	case '=':	return (eval(e->e.o.left) == eval(e->e.o.right));
X	case '>':	return (eval(e->e.o.left) > eval(e->e.o.right));
X	case '&':	return (eval(e->e.o.left) && eval(e->e.o.right));
X	case '|':	return (eval(e->e.o.left) || eval(e->e.o.right));
X	case IF:
X	case '?':	return eval(e->e.o.left) ? eval(e->e.o.right->e.o.left)
X						: eval(e->e.o.right->e.o.right);
X	case 'm':	return (-eval(e->e.o.right));
X	case 'f':	return (eval(e->e.o.right));
X	case '~':	return (eval(e->e.o.right) == 0.0);
X	case 'k':	return (e->e.k);
X	case 'v':	return (e->e.v.vp->v);
X	case INDEX:
X	case LOOKUP:
X	case HLOOKUP:
X	case VLOOKUP:
X	    {	register r,c;
X		register maxr, maxc;
X		register minr, minc;
X		maxr = e->e.o.right->e.r.right.vp -> row;
X		maxc = e->e.o.right->e.r.right.vp -> col;
X		minr = e->e.o.right->e.r.left.vp -> row;
X		minc = e->e.o.right->e.r.left.vp -> col;
X		if (minr>maxr) r = maxr, maxr = minr, minr = r;
X		if (minc>maxc) c = maxc, maxc = minc, minc = c;
X		switch(e->op){
X		case LOOKUP:
X		    return dolookup(e->e.o.left, minr, minc, maxr, maxc,
X				     minr==maxr, minc==maxc);
X		case HLOOKUP:
X	            return dolookup(e->e.o.left->e.o.left, minr,minc,maxr,maxc,
X			(int) eval(e->e.o.left->e.o.right), 0);
X		case VLOOKUP:
X	            return dolookup(e->e.o.left->e.o.left, minr,minc,maxr,maxc,
X			0, (int) eval(e->e.o.left->e.o.right));
X		case INDEX:
X		    return doindex(eval(e->e.o.left), minr, minc, maxr, maxc);
X		}
X	    }
X	case REDUCE | '+':
X 	case REDUCE | '*':
X 	case REDUCE | 'a':
X 	case REDUCE | 'c':
X 	case REDUCE | 's':
X	case REDUCE | MAX:
X	case REDUCE | MIN:
X	    {	register r,c;
X		register maxr, maxc;
X		register minr, minc;
X		maxr = e->e.r.right.vp -> row;
X		maxc = e->e.r.right.vp -> col;
X		minr = e->e.r.left.vp -> row;
X		minc = e->e.r.left.vp -> col;
X		if (minr>maxr) r = maxr, maxr = minr, minr = r;
X		if (minc>maxc) c = maxc, maxc = minc, minc = c;
X	        switch (e->op) {
X	            case REDUCE | '+': return dosum(minr, minc, maxr, maxc);
X 	            case REDUCE | '*': return doprod(minr, minc, maxr, maxc);
X 	            case REDUCE | 'a': return doavg(minr, minc, maxr, maxc);
X 	            case REDUCE | 'c': return docount(minr, minc, maxr, maxc);
X 	            case REDUCE | 's': return dostddev(minr, minc, maxr, maxc);
X 	            case REDUCE | MAX: return domax(minr, minc, maxr, maxc);
X 	            case REDUCE | MIN: return domin(minr, minc, maxr, maxc);
X		}
X	    }
X	case ABS:	 return (fn1_eval( fabs, eval(e->e.o.right)));
X	case ACOS:	 return (fn1_eval( acos, eval(e->e.o.right)));
X	case ASIN:	 return (fn1_eval( asin, eval(e->e.o.right)));
X	case ATAN:	 return (fn1_eval( atan, eval(e->e.o.right)));
X	case ATAN2:	 return (fn2_eval( atan2, eval(e->e.o.left), eval(e->e.o.right)));
X	case CEIL:	 return (fn1_eval( ceil, eval(e->e.o.right)));
X	case COS:	 return (fn1_eval( cos, eval(e->e.o.right)));
X	case EXP:	 return (fn1_eval( exp, eval(e->e.o.right)));
X	case FABS:	 return (fn1_eval( fabs, eval(e->e.o.right)));
X	case FLOOR:	 return (fn1_eval( floor, eval(e->e.o.right)));
X	case HYPOT:	 return (fn2_eval( hypot, eval(e->e.o.left), eval(e->e.o.right)));
X	case LOG:	 return (fn1_eval( log, eval(e->e.o.right)));
X	case LOG10:	 return (fn1_eval( log10, eval(e->e.o.right)));
X	case POW:	 return (fn2_eval( pow, eval(e->e.o.left), eval(e->e.o.right)));
X	case SIN:	 return (fn1_eval( sin, eval(e->e.o.right)));
X	case SQRT:	 return (fn1_eval( sqrt, eval(e->e.o.right)));
X	case TAN:	 return (fn1_eval( tan, eval(e->e.o.right)));
X	case DTR:	 return (dtr(eval(e->e.o.right)));
X	case RTD:	 return (rtd(eval(e->e.o.right)));
X	case RND:	 {
X			    double temp;
X			    temp = eval(e->e.o.right);
X			    return(temp-floor(temp) < 0.5 ?
X					     floor(temp) : ceil(temp));
X			}
X 	case ROUND:	{
X			    double temp = eval(e->e.o.left);
X			    int prec = (int) eval(e->e.o.right), scal = 1;
X			    while (prec-- > 0) scal *= 10;
X			    temp *= scal;
X			    temp = ((temp-floor(temp)) < 0.5 ?
X				    floor(temp) : ceil(temp));
X			    return(temp / scal);
X			}
X	case FV:
X	case PV:
X	case PMT:	return(finfunc(e->op,eval(e->e.o.left),
X				   eval(e->e.o.right->e.o.left),
X				      eval(e->e.o.right->e.o.right)));
X	case HOUR:	 return (dotime(HOUR, eval(e->e.o.right)));
X	case MINUTE:	 return (dotime(MINUTE, eval(e->e.o.right)));
X	case SECOND:	 return (dotime(SECOND, eval(e->e.o.right)));
X	case MONTH:	 return (dotime(MONTH, eval(e->e.o.right)));
X	case DAY:	 return (dotime(DAY, eval(e->e.o.right)));
X	case YEAR:	 return (dotime(YEAR, eval(e->e.o.right)));
X	case NOW:	 return (dotime(NOW, (double)0.0));
X	case DTS:	 return (dodts((int)eval(e->e.o.left),
X				 (int)eval(e->e.o.right->e.o.left),
X				 (int)eval(e->e.o.right->e.o.right)));
X	case TTS:	 return (dotts((int)eval(e->e.o.left),
X				 (int)eval(e->e.o.right->e.o.left),
X				 (int)eval(e->e.o.right->e.o.right)));
X	case STON:	 return (doston(seval(e->e.o.right)));
X	case EQS:        return (doeqs(seval(e->e.o.right),seval(e->e.o.left)));
X	case LMAX:	 return dolmax(e);
X	case LMIN:	 return dolmin(e);
X	case NVAL:       return (donval(seval(e->e.o.left),eval(e->e.o.right)));
X	default:	 error ("Illegal numeric expression");
X			 exprerr = 1;
X    }
X    return((double)0.0);
}
X
#ifdef SIGVOID
void
#endif
eval_fpe(signo) /* Trap for FPE errors in eval */
int signo;
{
#ifdef IEEE_MATH
X	(void)fpsetsticky((fp_except)0); 		/* Clear exception */
#endif /* IEEE_MATH */
X	longjmp(fpe_save, 1);
}
X
double fn1_eval(fn, arg)
double (*fn)();
double arg;
{
X	double res;
X	errno = 0;
X	res = (*fn)(arg);
X	if(errno)
X	  eval_fpe(0);
X
X	return res;
}
X
double fn2_eval(fn, arg1, arg2)
double (*fn)();
double arg1, arg2;
{
X	double res;
X	errno = 0;
X	res = (*fn)(arg1, arg2);
X	if(errno) 
X	    eval_fpe(0);
X
X	return res;
}
X
/* 
X * Rules for string functions:
X * Take string arguments which they xfree.
X * All returned strings are assumed to be xalloced.
X */
X
char *
docat(s1, s2)
register char *s1, *s2;
{
X    register char *p;
X    char *arg1, *arg2;
X
X    if (!s1 && !s2)
X	return((char *)0);
X    arg1 = s1 ? s1 : "";
X    arg2 = s2 ? s2 : "";
X    p = xmalloc((unsigned)(strlen(arg1)+strlen(arg2)+1));
X    (void) strcpy(p, arg1);
X    (void) strcat(p, arg2);
X    if (s1)
X        xfree(s1);
X    if (s2)
X        xfree(s2);
X    return(p);
}
X
char *
dodate(tloc)
long tloc;
{
X    char *tp;
X    char *p;
X
X    tp = ctime(&tloc);
X    tp[24] = '\0';
X    p = xmalloc((unsigned)25);
X    (void) strcpy(p, tp);
X    return(p);
}
X
X
char *
dofmt(fmtstr, v)
char *fmtstr;
double v;
{
X    char buff[FBUFLEN];
X    char *p;
X
X    if (!fmtstr)
X	return((char *)0);
X    (void)sprintf(buff, fmtstr, v);
X    p = xmalloc((unsigned)(strlen(buff)+1));
X    (void) strcpy(p, buff);
X    xfree(fmtstr);
X    return(p);
}
X
X
/*
X * Given a command name and a value, run the command with the given value and
X * read and return its first output line (only) as an allocated string, always
X * a copy of prevstr, which is set appropriately first unless external
X * functions are disabled, in which case the previous value is used.  The
X * handling of prevstr and freeing of command is tricky.  Returning an
X * allocated string in all cases, even if null, insures cell expressions are
X * written to files, etc.
X */
X
#ifdef VMS
char *
doext(command, value)
char *command;
double value;
{
X    error("Warning: External functions unavailable on VMS");
X    if (command)
X	xfree(command);
X    return (strcpy (xmalloc((unsigned) 1), "\0"));
}
X
#else /* VMS */
X
char *
doext (command, value)
char   *command;
double value;
{
X    static char *prevstr = (char *)0;	/* previous result */
X    char buff[FBUFLEN];		/* command line/return, not permanently alloc */
X
X    if (!prevstr) {
X	prevstr = xmalloc((unsigned)1);
X	*prevstr = '\0';
X    }
X    if (!extfunc)    {
X	error ("Warning: external functions disabled; using %s value",
X		prevstr ? "previous" : "null");
X
X	if (command) xfree (command);
X    } else {
X	if (prevstr) xfree (prevstr);		/* no longer needed */
X	prevstr = '\0';
X
X	if ((! command) || (! *command)) {
X	    error ("Warning: external function given null command name");
X	    if (command) xfree (command);
X	} else {
X	    FILE *pp;
X
X	    (void) sprintf (buff, "%s %g", command, value); /* build cmd line */
X	    xfree (command);
X
X	    error ("Running external function...");
X	    (void) refresh();
X
X	    if ((pp = popen (buff, "r")) == (FILE *) NULL)	/* run it */
X		error ("Warning: running \"%s\" failed", buff);
X	    else {
X		if (fgets (buff, sizeof(buff)-1, pp) == NULL)	/* one line */
X		    error ("Warning: external function returned nothing");
X		else {
X		    char *cp;
X
X		    error ("");				/* erase notice */
X		    buff[sizeof(buff)-1] = '\0';
X
X		    if (cp = strchr (buff, '\n'))	/* contains newline */
X			*cp = '\0';			/* end string there */
X
X		    (void) strcpy (prevstr = 
X			 xmalloc ((unsigned) (strlen (buff) + 1)), buff);
X			 /* save alloc'd copy */
X		}
X		(void) pclose (pp);
X
X	    } /* else */
X	} /* else */
X    } /* else */
X    return (strcpy (xmalloc ((unsigned) (strlen (prevstr) + 1)), prevstr));
}
X
#endif /* VMS */
X
X
/*
X * Given a string representing a column name and a value which is a column
X * number, return the selected cell's string value, if any.  Even if none,
X * still allocate and return a null string so the cell has a label value so
X * the expression is saved in a file, etc.
X */
X
char *
dosval (colstr, rowdoub)
X    char *colstr;
X    double rowdoub;
{
X    struct ent *ep;
X    char *label;
X
X    label = (ep = getent (colstr, rowdoub)) ? (ep -> label) : "";
X    return (strcpy (xmalloc ((unsigned) (strlen (label) + 1)), label));
}
X
X
/*
X * Substring:  Note that v1 and v2 are one-based to users, but zero-based
X * when calling this routine.
X */
X
char *
dosubstr(s, v1, v2)
char *s;
register int v1,v2;
{
X    register char *s1, *s2;
X    char *p;
X
X    if (!s)
X	return((char *)0);
X
X    if (v2 >= strlen (s))		/* past end */
X	v2 =  strlen (s) - 1;		/* to end   */
X
X    if (v1 < 0 || v1 > v2) {		/* out of range, return null string */
X	xfree(s);
X	p = xmalloc((unsigned)1);
X	p[0] = '\0';
X	return(p);
X    }
X    s2 = p = xmalloc((unsigned)(v2-v1+2));
X    s1 = &s[v1];
X    for(; v1 <= v2; s1++, s2++, v1++)
X	*s2 = *s1;
X    *s2 = '\0';
X    xfree(s);
X    return(p);
}
X
char *
seval(se)
register struct enode *se;
{
X    register char *p;
X
X    if (se == (struct enode *)0) return (char *)0;
X    switch (se->op) {
X	case O_SCONST: p = xmalloc((unsigned)(strlen(se->e.s)+1));
X		     (void) strcpy(p, se->e.s);
X		     return(p);
X	case O_VAR:    {
X			struct ent *ep;
X			ep = se->e.v.vp;
X
X			if (!ep->label)
X			    return((char *)0);
X			p = xmalloc((unsigned)(strlen(ep->label)+1));
X			(void) strcpy(p, ep->label);
X			return(p);
X		     }
X	case '#':    return(docat(seval(se->e.o.left), seval(se->e.o.right)));
X	case 'f':    return(seval(se->e.o.right));
X	case IF:
X	case '?':    return(eval(se->e.o.left) ? seval(se->e.o.right->e.o.left)
X					     : seval(se->e.o.right->e.o.right));
X	case DATE:   return(dodate((long)(eval(se->e.o.right))));
X	case FMT:    return(dofmt(seval(se->e.o.left), eval(se->e.o.right)));
X 	case STINDEX:
X 		{	register r,c;
X 		register maxr, maxc;
X 		register minr, minc;
X 		maxr = se->e.o.right->e.r.right.vp -> row;
X 		maxc = se->e.o.right->e.r.right.vp -> col;
X 		minr = se->e.o.right->e.r.left.vp -> row;
X 		minc = se->e.o.right->e.r.left.vp -> col;
X 		if (minr>maxr) r = maxr, maxr = minr, minr = r;
X 		if (minc>maxc) c = maxc, maxc = minc, minc = c;
X 	        return dostindex(eval(se->e.o.left), minr, minc, maxr, maxc);
X		}
X	case EXT:    return(doext(seval(se->e.o.left), eval(se->e.o.right)));
X	case SVAL:   return(dosval(seval(se->e.o.left), eval(se->e.o.right)));
X	case SUBSTR: return(dosubstr(seval(se->e.o.left),
X			    (int)eval(se->e.o.right->e.o.left) - 1,
X			    (int)eval(se->e.o.right->e.o.right) - 1));
X	default:
X		     error ("Illegal string expression");
X		     exprerr = 1;
X		     return((char *)0);
X	}
}
X
/*
X * The graph formed by cell expressions which use other cells's values is not
X * evaluated "bottom up".  The whole table is merely re-evaluated cell by cell,
X * top to bottom, left to right, in RealEvalAll().  Each cell's expression uses
X * constants in other cells.  However, RealEvalAll() notices when a cell gets a
X * new numeric or string value, and reports if this happens for any cell.
X * EvalAll() repeats calling RealEvalAll() until there are no changes or the
X * evaluation count expires.
X */
X
int propagation = 10;	/* max number of times to try calculation */
X
void
setiterations(i)
int i;
{
X	if(i<1) {
X		error("iteration count must be at least 1");
X		propagation = 1;
X		}
X	else propagation = i;
}
X
void
EvalAll () {
X      int lastcnt, repct = 0;
X  
X     while ((lastcnt = RealEvalAll()) && (repct++ <= propagation));
X     if((propagation>1)&& (lastcnt >0 ))
X 	    error("Still changing after %d iterations",propagation-1);
}
X
/*
X * Evaluate all cells which have expressions and alter their numeric or string
X * values.  Return the number of cells which changed.
X */
X
int 
RealEvalAll () {
X    register int i,j;
X    int chgct = 0;
X    register struct ent *p;
X
X    (void) signal(SIGFPE, eval_fpe);
#ifdef EXPRTREE
X    for (p = firstev; p; p = p->evnext)
X	    RealEvalOne(p, &chgct);
#else
X    if(calc_order == BYROWS ) {
X	for (i=0; i<=maxrow; i++)
X	    for (j=0; j<=maxcol; j++)
X		if ((p=tbl[i][j]) && p->expr) RealEvalOne(p,i,j, &chgct);
X    }
X    else if ( calc_order == BYCOLS ) {
X	for (j=0; j<=maxcol; j++)
X	{   for (i=0; i<=maxrow; i++)
X		if ((p=tbl[i][j]) && p->expr) RealEvalOne(p,i,j, &chgct);
X	}
X    }
X    else error("Internal error calc_order");
#endif
X 
X    (void) signal(SIGFPE, quit);
X    return(chgct);
}
X
void
#ifdef EXPRTREE
RealEvalOne(p, chgct)
register struct ent *p;
int *chgct;
#else
RealEvalOne(p, i, j, chgct)
register struct ent *p;
int i, j, *chgct;
#endif
{
X	if (p->flags & is_strexpr) {
X	    char *v;
X	    if (setjmp(fpe_save)) {
#ifdef EXPRTREE
X		error("Floating point exception %s", v_name(p->row, p->col));
#else
X		error("Floating point exception %s", v_name(i, j));
#endif
X		v = "";
X	    } else {
X		v = seval(p->expr);
X	    }
X	    if (!v && !p->label) /* Everything's fine */
X		return;
X	    if (!p->label || !v || strcmp(v, p->label) != 0) {
X		(*chgct)++;
X		p->flags |= is_changed;
X		changed++;
X	    }
X	    if(p->label)
X		xfree(p->label);
X	    p->label = v;
X	} else {
X	    double v;
X	    if (setjmp(fpe_save)) {
#ifdef EXPRTREE
X		error("Floating point exception %s", v_name(p->row, p->col));
#else
X		error("Floating point exception %s", v_name(i, j));
#endif
X		v = (double)0.0;
X	    } else {
X		v = eval (p->expr);
X	    }
X	    if (v != p->v) {
X		p->v = v; (*chgct)++;
X		p->flags |= is_changed|is_valid;
X		changed++;
X	    }
X	}
}
X
struct enode *
new(op, a1, a2)
int	op;
struct enode *a1, *a2;
{
X    register struct enode *p;
X    p = (struct enode *) xmalloc ((unsigned)sizeof (struct enode));
X    p->op = op;
X    p->e.o.left = a1;
X    p->e.o.right = a2;
X    return p;
}
X
struct enode *
new_var(op, a1)
int	op;
struct ent_ptr a1;
{
X    register struct enode *p;
X    p = (struct enode *) xmalloc ((unsigned)sizeof (struct enode));
X    p->op = op;
X    p->e.v = a1;
X    return p;
}
X
struct enode *
new_range(op, a1)
int	op;
struct range_s a1;
{
X    register struct enode *p;
X    p = (struct enode *) xmalloc ((unsigned)sizeof (struct enode));
X    p->op = op;
X    p->e.r = a1;
X    return p;
}
X
struct enode *
new_const(op, a1)
int	op;
double a1;
{
X    register struct enode *p;
X    p = (struct enode *) xmalloc ((unsigned)sizeof (struct enode));
X    p->op = op;
X    p->e.k = a1;
X    return p;
}
X
struct enode *
new_str(s)
char *s;
{
X    register struct enode *p;
X
X    p = (struct enode *) xmalloc ((unsigned)sizeof(struct enode));
X    p->op = O_SCONST;
X    p->e.s = s;
X    return(p);
}
X
void
copy(dv1, dv2, v1, v2)
struct ent *dv1, *dv2, *v1, *v2;
{
X    int minsr, minsc;
X    int maxsr, maxsc;
X    int mindr, mindc;
X    int maxdr, maxdc;
X    int vr, vc;
X    int r, c;
X
X    mindr = dv1->row;
X    mindc = dv1->col;
X    maxdr = dv2->row;
X    maxdc = dv2->col;
X    if (mindr>maxdr) r = maxdr, maxdr = mindr, mindr = r;
X    if (mindc>maxdc) c = maxdc, maxdc = mindc, mindc = c;
X    maxsr = v2->row;
X    maxsc = v2->col;
X    minsr = v1->row;
X    minsc = v1->col;
X    if (minsr>maxsr) r = maxsr, maxsr = minsr, minsr = r;
X    if (minsc>maxsc) c = maxsc, maxsc = minsc, minsc = c;
X    checkbounds(&maxdr, &maxdc);
X
X    erase_area(mindr, mindc, maxdr, maxdc);
X    if (minsr == maxsr && minsc == maxsc) {
X	/* Source is a single cell */
X	for(vr = mindr; vr <= maxdr; vr++)
X	    for (vc = mindc; vc <= maxdc; vc++)
X		copyrtv(vr, vc, minsr, minsc, maxsr, maxsc);
X    } else if (minsr == maxsr) {
X	/* Source is a single row */
X	for (vr = mindr; vr <= maxdr; vr++)
X	    copyrtv(vr, mindc, minsr, minsc, maxsr, maxsc);
X    } else if (minsc == maxsc) {
X	/* Source is a single column */
X	for (vc = mindc; vc <= maxdc; vc++)
X	    copyrtv(mindr, vc, minsr, minsc, maxsr, maxsc);
X    } else {
X	/* Everything else */
X	copyrtv(mindr, mindc, minsr, minsc, maxsr, maxsc);
X    }
X    sync_refs();
}
X
void
copyrtv(vr, vc, minsr, minsc, maxsr, maxsc)
int vr, vc, minsr, minsc, maxsr, maxsc;
{
X    register struct ent *p;
X    register struct ent *n;
X    register int sr, sc;
X    register int dr, dc;
X
X    for (dr=vr, sr=minsr; sr<=maxsr; sr++, dr++)
X	for (dc=vc, sc=minsc; sc<=maxsc; sc++, dc++) {
X	    if (p = *ATBL(tbl, sr, sc))
X	    {	n = lookat (dr, dc);
X		(void) clearent(n);
X		copyent( n, p, dr - sr, dc - sc);
X	    }
X	    else
X	    if (n = *ATBL(tbl, dr, dc))
X		(void) clearent(n);
X	}
}
X
void
eraser(v1, v2)
struct ent *v1, *v2;
{
X	FullUpdate++;
X	flush_saved();
X	erase_area(v1->row, v1->col, v2->row, v2->col);
X	sync_refs();
}
X
/* Goto subroutines */
X
void
g_free()
{
X    switch (gs.g_type) {
X    case G_STR: xfree(gs.g_s); break;
X    default: break;
X    }
X    gs.g_type = G_NONE;
}
X
void
go_last()
{
X    switch (gs.g_type) {
X    case G_NONE:
X		error("Nothing to repeat"); break;
X    case G_NUM:
X		num_search(gs.g_n);
X		break;
X    case  G_CELL:
X		moveto(gs.g_row, gs.g_col);
X	    	break;
X    case  G_STR: 
X		gs.g_type = G_NONE;	/* Don't free the string */
X   	    	str_search(gs.g_s); 
X	   	break;
X
X    default: error("go_last: internal error");
X    }
}
X
void
moveto(row, col)
int row, col;
{
X    currow = row;
X    curcol = col;
X    g_free();
X    gs.g_type = G_CELL;
X    gs.g_row = currow;
X    gs.g_col = curcol;
}
X
void
num_search(n)
double n;
{
X    register struct ent *p;
X    register int r,c;
X    int	endr, endc;
X
X    g_free();
X    gs.g_type = G_NUM;
X    gs.g_n = n;
X
X    if (currow > maxrow)
X	endr = maxrow ? maxrow-1 : 0;
X    else
X	endr = currow;
X    if (curcol > maxcol)
X	endc = maxcol ? maxcol-1 : 0;
X    else
X	endc = curcol;
X    r = endr;
X    c = endc;
X    do {
X	if (c < maxcol)
X	    c++;
X	else {
X	    if (r < maxrow) {
X		while(++r < maxrow && row_hidden[r]) /* */;
X		c = 0;
X	    } else {
X		r = 0;
X		c = 0;
X	    }
X	}
X	if (r == endr && c == endc) {
X	    error("Number not found");
X	    return;
X	}
X	p = *ATBL(tbl, r, c);
X    } while(col_hidden[c] || !p || p && (!(p->flags & is_valid) 
X                                        || (p->flags&is_valid) && p->v != n));
X    currow = r;
X    curcol = c;
}
X
void
str_search(s)
char *s;
{
X    register struct ent *p;
X    register int r,c;
X    int	endr, endc;
X    char *tmp;
X
#if defined(BSD42) || defined(BSD43)
X    if ((tmp = re_comp(s)) != (char *)0) {
X	xfree(s);
X	error(tmp);
X	return;
X    }
#endif
#if defined(SYSV2) || defined(SYSV3)
X    if ((tmp = regcmp(s, (char *)0)) == (char *)0) {
X	xfree(s);
X	error("Invalid search string");
X	return;
X    }
#endif
X    g_free();
X    gs.g_type = G_STR;
X    gs.g_s = s;
X    if (currow > maxrow)
X	endr = maxrow ? maxrow-1 : 0;
X    else
X	endr = currow;
X    if (curcol > maxcol)
X	endc = maxcol ? maxcol-1 : 0;
X    else
X	endc = curcol;
X    r = endr;
X    c = endc;
X    do {
X	if (c < maxcol)
X	    c++;
X	else {
X	    if (r < maxrow) {
X		while(++r < maxrow && row_hidden[r]) /* */;
X		c = 0;
X	    } else {
X		r = 0;
X		c = 0;
X	    }
X	}
X	if (r == endr && c == endc) {
X	    error("String not found");
#if defined(SYSV2) || defined(SYSV3)
X	    free(tmp);
#endif
X	    return;
X	}
X	p = *ATBL(tbl, r, c);
X    } while(col_hidden[c] || !p || p && (!(p->label) 
#if defined(BSD42) || defined(BSD43)
X		  			|| (re_exec(p->label) == 0)));
#else
#if defined(SYSV2) || defined(SYSV3)
X                                       || (regex(tmp, p->label) == (char *)0)));
#else
X                                       || (strcmp(s, p->label) != 0)));
#endif
#endif
X    currow = r;
X    curcol = c;
#if defined(SYSV2) || defined(SYSV3)
X    free(tmp);
#endif
}
X
void
fill (v1, v2, start, inc)
struct ent *v1, *v2;
double start, inc;
{
X    register r,c;
X    register struct ent *n;
X    int maxr, maxc;
X    int minr, minc;
X
X    maxr = v2->row;
X    maxc = v2->col;
X    minr = v1->row;
X    minc = v1->col;
X    if (minr>maxr) r = maxr, maxr = minr, minr = r;
X    if (minc>maxc) c = maxc, maxc = minc, minc = c;
X    checkbounds(&maxr, &maxc);
X    if (minr < 0) minr = 0;
X    if (minr < 0) minr = 0;
X
X    FullUpdate++;
X    if( calc_order == BYROWS ) {
X    for (r = minr; r<=maxr; r++)
X	for (c = minc; c<=maxc; c++) {
X	    n = lookat (r, c);
X	    (void) clearent(n);
X	    n->v = start;
X	    start += inc;
X	    n->flags |= (is_changed|is_valid);
X	}
X    }
X    else if ( calc_order == BYCOLS ) {
X    for (c = minc; c<=maxc; c++)
X	for (r = minr; r<=maxr; r++) {
X	    n = lookat (r, c);
X	    (void) clearent(n);
X	    n->v = start;
X	    start += inc;
X	    n->flags |= (is_changed|is_valid);
X	}
X    }
X    else error(" Internal error calc_order");
X    changed++;
}
X
void
let (v, e)
struct ent *v;
struct enode *e;
{
X    double val;
X
X    exprerr = 0;
X    (void) signal(SIGFPE, eval_fpe);
X    if (setjmp(fpe_save)) {
X	error ("Floating point exception in cell %s", v_name(v->row, v->col));
X	val = (double)0.0;
X    } else {
X	val = eval(e);
X    }
X    (void) signal(SIGFPE, quit);
X    if (exprerr) {
X	efree((struct ent *)0, e);
X	return;
X    }
X    if (constant(e)) {
X	if (!loading)
X	    v->v = val * prescale;
X	else
X	    v->v = val;
X	if (!(v->flags & is_strexpr)) {
X            efree(v, v->expr);
X	    v->expr = (struct enode *)0;
X	}
X	efree((struct ent *)0, e);
X        v->flags |= (is_changed|is_valid);
X        changed++;
X        modflg++;
X	return;
X    }
X    efree (v, v->expr);
X    v->expr = e;
X    v->flags |= (is_changed|is_valid);
X    v->flags &= ~is_strexpr;
X
#ifdef EXPRTREE
X    totoptree(v);
#endif
X    changed++;
X    modflg++;
}
X
void
slet (v, se, flushdir)
struct ent *v;
struct enode *se;
int flushdir;
{
X    char *p;
X
X    exprerr = 0;
X    (void) signal(SIGFPE, eval_fpe);
X    if (setjmp(fpe_save)) {
X	error ("Floating point exception in cell %s", v_name(v->row, v->col));
X	p = "";
X    } else {
X	p = seval(se);
X    }
X    (void) signal(SIGFPE, quit);
X    if (exprerr) {
X	efree((struct ent *)0, se);
X	return;
X    }
X    if (constant(se)) {
X	label(v, p, flushdir);
X	if (p)
X	    xfree(p);
X	efree((struct ent *)0, se);
X	if (v->flags & is_strexpr) {
X            efree (v, v->expr);
X	    v->expr = (struct enode *)0;
X	    v->flags &= ~is_strexpr;
X	}
X	return;
X    }
X    efree (v, v->expr);
X    v->expr = se;
X    v->flags |= (is_changed|is_strexpr);
X    if (flushdir<0) v->flags |= is_leftflush;
X    else v->flags &= ~is_leftflush;
X
#ifdef EXPRTREE
X    totoptree();
#endif
X    FullUpdate++;
X    changed++;
X    modflg++;
}
X
#ifdef EXPRTREE
/*
X * put an expression in the expression tree, only the top of each branch is
X * in the firstev list
X */
totoptree(v)
struct	ent *v;
{
X    int	right;
X    int	left;
X    if (!v->expr)
X	return;
X
#ifdef notdef
X    right = FALSE;
X    left = FALSE;
X    switch(v->expr->op)
X    {
X		/* no real expression */
X	case 'v':
X		if (v->expr->o.v->evnext)
X			evdel(v->expr->o.v);
X	case 'k':
X	case LMAX:
X	case LMIN:
X	case NOW:
X	case O_SCONST:
X	case O_VAR:
X	default:
X		return;
X
X		/* left && right */
X	case '#':
X	case '%':
X	case '&':
X	case '*':
X	case '+':
X	case '-':
X	case '/':
X	case '<':
X	case '=':
X	case '>':
X	case '?':
X	case '^':
X	case '|':
X	case ATAN2:
X	case DTS:
X	case EQS:
X	case EXT:
X	case FMT:
X	case FV:
X	case HYPOT:
X	case IF:
X	case NVAL:
X	case PMT:
X	case POW:
X	case PV:
X	case REDUCE | '*':
X	case REDUCE | '+':
X	case REDUCE | 'a':
X	case REDUCE | 'c':
X	case REDUCE | 's':
X	case REDUCE | MAX:
X	case REDUCE | MIN:
X	case ROUND:
X	case STINDEX:
X	case SUBSTR:
X	case SVAL:
X	case TTS:
X		left = right = TRUE;
X		break;
X		/* right only */
X	case 'f':
X	case 'm':
X	case '~':
X	case ABS:
X	case ACOS:
X	case ASIN:
X	case ATAN:
X	case CEIL:
X	case COS:
X	case DATE:
X	case DAY:
X	case DTR:
X	case EXP:
X	case FABS:
X	case FLOOR:
X	case HLOOKUP:
X	case HOUR:
X	case IF:
X	case INDEX:
X	case LOG10:
X	case LOG:
X	case LOOKUP:
X	case MINUTE:
X	case MONTH:
X	case RND:
X	case RTD:
X	case SECOND:
X	case SIN:
X	case SQRT:
X	case STON:
X	case TAN:
X	case VLOOKUP:
X	case YEAR:
X		right = TRUE;
X		break;
X    }
X	/* for now insert at the beginning of the list */
X    v->evnext = firstev;
X    v->evprev = (struct ent *)0;
X    if (firstev)
X	firstev->evprev = v;
X    firstev = v;
#endif
X    firstev = v;
}
#endif /* EXPRTREE*/
X
void
hide_row(arg)
int arg;
{
X    if (arg < 0) {
X	error("Invalid Range");
X	return;
X    }
X    if (arg >= maxrows-1)
X    {
X	if (!growtbl(GROWROW, arg+1, 0))
X	{	error("You can't hide the last row");
X		return;
X	}
X    }
X    FullUpdate++;
X    row_hidden[arg] = 1;
}
X
void
hide_col(arg)
int arg;
{
X    if (arg < 0) {
X	error("Invalid Range");
X	return;
X    }
X    if (arg >= maxcols-1)
X    {	if ((arg >= ABSMAXCOLS-1) || !growtbl(GROWCOL, 0, arg+1))
X	{	error("You can't hide the last col");
X		return;
X	}
X    }
X    FullUpdate++;
X    col_hidden[arg] = 1;
}
X
void
clearent (v)
struct ent *v;
{
X    if (!v)
X	return;
X    label(v,"",-1);
X    v->v = (double)0;
X    if (v->expr)
X	efree(v, v->expr);
X    v->expr = (struct enode *)0;
X    v->flags |= (is_changed);
X    v->flags &= ~(is_valid);
X    changed++;
X    modflg++;
}
X
/*
X * Say if an expression is a constant (return 1) or not.
X */
int
constant (e)
X    register struct enode *e;
{
X    return ((e == (struct enode *)0)
X	 || ((e -> op) == O_CONST)
X	 || ((e -> op) == O_SCONST)
X	 || (((e -> op) != O_VAR)
X	  && (((e -> op) & REDUCE) != REDUCE)
X	  && constant (e -> e.o.left)
X	  && constant (e -> e.o.right)
X	  && (e -> op != EXT)	 /* functions look like constants but aren't */
X	  && (e -> op != NVAL)
X	  && (e -> op != SVAL)
X	  && (e -> op != NOW)));
}
X
void
efree (v, e)
struct ent *v;
struct enode *e;
{
X    if (e) {
X	if (e->op != O_VAR && e->op !=O_CONST && e->op != O_SCONST
X		&& (e->op & REDUCE) != REDUCE) {
X	    efree(v, e->e.o.left);
X	    efree(v, e->e.o.right);
X	}
X	if (e->op == O_SCONST && e->e.s)
X	    xfree(e->e.s);
X	xfree ((char *)e);
X
#ifdef EXPRTREE
X	/* delete this cell from the eval list */
X	if (v)
X	{	if (v->evprev)
X			v->evprev->evnext = v->evnext;
X		if (v->evnext)
X			v->evnext->evprev = v->evprev;
X	}
#endif /* EXPRTREE */
X    }
}
X
void
label (v, s, flushdir)
register struct ent *v;
register char *s;
int	flushdir;
{
X    if (v) {
X	if (flushdir==0 && v->flags&is_valid) {
X	    register struct ent *tv;
X	    if (v->col>0 && ((tv=lookat(v->row,v->col-1))->flags&is_valid)==0)
X		v = tv, flushdir = 1;
X	    else if (((tv=lookat (v->row,v->col+1))->flags&is_valid)==0)
X		v = tv, flushdir = -1;
X	    else flushdir = -1;
X	}
X	if (v->label) xfree((char *)(v->label));
X	if (s && s[0]) {
X	    v->label = xmalloc ((unsigned)(strlen(s)+1));
X	    (void) strcpy (v->label, s);
X	} else
X	    v->label = (char *)0;
X	if (flushdir<0) v->flags |= is_leftflush;
X	else v->flags &= ~is_leftflush;
X	FullUpdate++;
X	modflg++;
X    }
}
X
void
decodev (v)
struct ent_ptr v; 
{
X	register struct range *r;
X
X	if (!v.vp) (void)sprintf (line+linelim,"VAR?");
X	else if ((r = find_range((char *)0, 0, v.vp, v.vp)) && !r->r_is_range)
X	    (void)sprintf(line+linelim, "%s", r->r_name);
X	else
X	    (void)sprintf (line+linelim, "%s%s%s%d",
X			v.vf & FIX_COL ? "$" : "",
X			coltoa(v.vp->col),
X			v.vf & FIX_ROW ? "$" : "",
X			v.vp->row);
X	linelim += strlen (line+linelim);
}
X
char *
coltoa(col)
int col;
{
X    static char rname[3];
X    register char *p = rname;
X
X    if (col > 25) {
X	*p++ = col/26 + 'A' - 1;
X	col %= 26;
X    }
X    *p++ = col+'A';
X    *p = '\0';
X    return(rname);
}
X
/*
X *	To make list elements come out in the same order
X *	they were entered, we must do a depth-first eval
X *	of the ELIST tree
X */
static void
decompile_list(p)
struct enode *p;
{
X	if (!p) return;
X	decompile_list(p->e.o.left);	/* depth first */
X        decompile(p->e.o.right, 0);
X	line[linelim++] = ',';
}
X
void
decompile(e, priority)
register struct enode *e;
int	priority;
{
X    register char *s;
X    if (e) {
X	int mypriority;
X	switch (e->op) {
X	default: mypriority = 99; break;
X	case '?': mypriority = 1; break;
X	case ':': mypriority = 2; break;
X	case '|': mypriority = 3; break;
X	case '&': mypriority = 4; break;
X	case '<': case '=': case '>': mypriority = 6; break;
X	case '+': case '-': case '#': mypriority = 8; break;
X	case '*': case '/': case '%': mypriority = 10; break;
X	case '^': mypriority = 12; break;
X	}
X	if (mypriority<priority) line[linelim++] = '(';
X	switch (e->op) {
X	case 'f':	for (s="fixed "; line[linelim++] = *s++;);
X			linelim--;
X			decompile (e->e.o.right, 30);
X			break;
X	case 'm':	line[linelim++] = '-';
X			decompile (e->e.o.right, 30);
X			break;
X	case '~':	line[linelim++] = '~';
X			decompile (e->e.o.right, 30);
X			break;
X	case 'v':	decodev (e->e.v);
X			break;
X	case 'k':	(void)sprintf (line+linelim,"%.15g",e->e.k);
X			linelim += strlen (line+linelim);
X			break;
X	case '$':	(void)sprintf (line+linelim, "\"%s\"", e->e.s);
X			linelim += strlen(line+linelim);
X			break;
X
X	case REDUCE | '+': range_arg( "@sum(", e); break;
X	case REDUCE | '*': range_arg( "@prod(", e); break;
X	case REDUCE | 'a': range_arg( "@avg(", e); break;
X	case REDUCE | 'c': range_arg( "@count(", e); break;
X	case REDUCE | 's': range_arg( "@stddev(", e); break;
X	case REDUCE | MAX: range_arg( "@max(", e); break;
X	case REDUCE | MIN: range_arg( "@min(", e); break;
X
X	case ABS:		one_arg( "@abs(", e); break;
X	case ACOS:	one_arg( "@acos(", e); break;
X	case ASIN:	one_arg( "@asin(", e); break;
X	case ATAN:	one_arg( "@atan(", e); break;
X	case ATAN2:	two_arg( "@atan2(", e); break;
X	case CEIL:	one_arg( "@ceil(", e); break;
X	case COS:	one_arg( "@cos(", e); break;
X	case EXP:	one_arg( "@exp(", e); break;
X	case FABS:	one_arg( "@fabs(", e); break;
X	case FLOOR:	one_arg( "@floor(", e); break;
X	case HYPOT:	two_arg( "@hypot(", e); break;
X	case LOG:	one_arg( "@ln(", e); break;
X	case LOG10:	one_arg( "@log(", e); break;
X	case POW:	two_arg( "@pow(", e); break;
X	case SIN:	one_arg( "@sin(", e); break;
X	case SQRT:	one_arg( "@sqrt(", e); break;
X	case TAN:	one_arg( "@tan(", e); break;
X	case DTR:	one_arg( "@dtr(", e); break;
X	case RTD:	one_arg( "@rtd(", e); break;
X	case RND:	one_arg( "@rnd(", e); break;
X	case ROUND:	two_arg( "@round(", e); break;
X	case HOUR:	one_arg( "@hour(", e); break;
X	case MINUTE:	one_arg( "@minute(", e); break;
X	case SECOND:	one_arg( "@second(", e); break;
X	case MONTH:	one_arg( "@month(", e); break;
X	case DAY:	one_arg( "@day(", e); break;
X	case YEAR:	one_arg( "@year(", e); break;
X	case DATE:	one_arg( "@date(", e); break;
X	case DTS:	three_arg( "@dts(", e); break;
X	case TTS:	three_arg( "@tts(", e); break;
X	case STON:	one_arg( "@ston(", e); break;
X	case FMT:	two_arg( "@fmt(", e); break;
X	case EQS:	two_arg( "@eqs(", e); break;
X	case NOW:	for ( s = "@now"; line[linelim++] = *s++;);
X			linelim--;
X			break;
X	case LMAX:	list_arg("@max(", e); break;
X	case LMIN: 	list_arg("@min(", e); break;
X	case FV:	three_arg("@fv(", e); break;
X	case PV:	three_arg("@pv(", e); break;
X	case PMT:	three_arg("@pmt(", e); break;
X	case NVAL:	two_arg("@nval(", e); break;
X	case SVAL:	two_arg("@sval(", e); break;
X	case EXT:	two_arg("@ext(", e); break;
X	case SUBSTR:	three_arg("@substr(", e); break;
X	case STINDEX:	index_arg("@stindex(", e); break;
X	case INDEX:	index_arg("@index(", e); break;
X	case LOOKUP:	index_arg("@lookup(", e); break;
X	case HLOOKUP:	two_arg_index("@hlookup(", e); break;
X	case VLOOKUP:	two_arg_index("@vlookup(", e); break;
X	case IF:	three_arg("@if(", e); break;
X	default:	decompile (e->e.o.left, mypriority);
X			line[linelim++] = e->op;
X			decompile (e->e.o.right, mypriority+1);
X			break;
X	}
X	if (mypriority<priority) line[linelim++] = ')';
X    } else line[linelim++] = '?';
}
X
void
index_arg(s, e)
char *s;
struct enode *e;
{
X    for (; line[linelim++] = *s++;);
X    linelim--;
X    decompile( e-> e.o.left, 0 );
X    range_arg(", ", e->e.o.right);
}
X
void
two_arg_index(s, e)
char *s;
struct enode *e;
{
X    for (; line[linelim++] = *s++;);
X    linelim--;
X    decompile( e->e.o.left->e.o.left, 0 );
X    range_arg(",", e->e.o.right);
X    linelim--;
X    line[linelim++] = ',';
X    decompile( e->e.o.left->e.o.right, 0 );
X    line[linelim++] = ')';
}
X
void
list_arg(s, e)
char *s;
struct enode *e;
{
X    for (; line[linelim++] = *s++;);
X    linelim--;
X
X    decompile (e->e.o.right, 0);
X    line[linelim++] = ',';
X    decompile_list(e->e.o.left);
X    line[linelim - 1] = ')';
}
X
void
one_arg(s, e)
char *s;
struct enode *e;
{
X    for (; line[linelim++] = *s++;);
X    linelim--;
X    decompile (e->e.o.right, 0);
X    line[linelim++] = ')';
}
X
void
two_arg(s,e)
char *s;
struct enode *e;
{
X    for (; line[linelim++] = *s++;);
X    linelim--;
X    decompile (e->e.o.left, 0);
X    line[linelim++] = ',';
X    decompile (e->e.o.right, 0);
X    line[linelim++] = ')';
}
X
void
three_arg(s,e)
char *s;
struct enode *e;
{
X    for (; line[linelim++] = *s++;);
X    linelim--;
X    decompile (e->e.o.left, 0);
X    line[linelim++] = ',';
X    decompile (e->e.o.right->e.o.left, 0);
X    line[linelim++] = ',';
X    decompile (e->e.o.right->e.o.right, 0);
X    line[linelim++] = ')';
}
X
void
range_arg(s,e)
char *s;
struct enode *e;
{
X    struct range *r;
X
X    for (; line[linelim++] = *s++;);
X    linelim--;
X    if ((r = find_range((char *)0, 0, e->e.r.left.vp,
X			     e->e.r.right.vp)) && r->r_is_range) {
X	(void)sprintf(line+linelim, "%s", r->r_name);
X	linelim += strlen(line+linelim);
X    } else {
X	decodev (e->e.r.left);
X	line[linelim++] = ':';
X	decodev (e->e.r.right);
X    }
X    line[linelim++] = ')';
}
X
void
editv (row, col)
int row, col;
{
X    register struct ent *p;
X
X    p = lookat (row, col);
X    (void)sprintf (line, "let %s = ", v_name(row, col));
X    linelim = strlen(line);
X    if (p->flags & is_strexpr || p->expr == 0) {
X	(void)sprintf (line+linelim, "%.15g", p->v);
X	linelim += strlen (line+linelim);
X    } else {
X        editexp(row,col);
X    }
}
X
void
editexp(row,col)
int row, col;
{
X    register struct ent *p;
X
X    p = lookat (row, col);
X    decompile (p->expr, 0);
X    line[linelim] = '\0';
}
X
void
edits (row, col)
int row, col;
{
X    register struct ent *p;
X
X    p = lookat (row, col);
X    (void)sprintf (line, "%sstring %s = ",
X			((p->flags&is_leftflush) ? "left" : "right"),
X			v_name(row, col));
X    linelim = strlen(line);
X    if (p->flags & is_strexpr && p->expr) {
X	editexp(row, col);
X    } else if (p->label) {
X        (void)sprintf (line+linelim, "\"%s\"", p->label);
X        linelim += strlen (line+linelim);
X    } else {
X        (void)sprintf (line+linelim, "\"");
X        linelim += 1;
X    }
}
X
#ifdef AMIGA
/*
X *	Author: S.J.Raybould	(sie@fulcrum.bt.co.uk)
X *
X *	Date: Thu 18th October 1990
X *
X *	I would have made this a macro but fn2_eval() takes a pointer
X *	to a function as an argument, so it must be a function.
X */
double
hypot(x, y)
double x, y;
{
X	return sqrt(x*x+y*y);
}
#endif
SHAR_EOF
chmod 0666 interp.c ||
echo 'restore of interp.c failed'
Wc_c="`wc -c < 'interp.c'`"
test 48439 -eq "$Wc_c" ||
	echo 'interp.c: original size 48439, current size' "$Wc_c"
rm -f _shar_wnt_.tmp
fi
# ============= lex.c ==============
if test -f 'lex.c' -a X"$1" != X"-c"; then
	echo 'x - skipping lex.c (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting lex.c (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'lex.c' &&
/*	SC	A Spreadsheet Calculator
SHAR_EOF
true || echo 'restore of lex.c failed'
fi
echo 'End of  part 3'
echo 'File lex.c is continued in part 4'
echo 4 > _shar_seq_.tmp
exit 0
--
Simon J Raybould    (sie@fulcrum.bt.co.uk)            //              {o.o}
                                                    \X/AMIGA           \-/
===========================================================================
British Telecom Fulcrum, Fordrough Lane, Birmingham, B9 5LD, ENGLAND.