[comp.sources.unix] v18i047: SC spreadsheet, version 6.1, Part03/04

rsalz@uunet.uu.net (Rich Salz) (03/22/89)

Submitted-by: Robert Bond <sequent!rgb>
Posting-number: Volume 18, Issue 47
Archive-name: sc6.1/part03

# This is a shell archive.  Remove anything before this line
# then unpack it by saving it in a file and typing "sh file"
# (Files unpacked will be owned by you and have default permissions).
# This archive contains the following files:
#	./gram.y
#	./interp.c
#	./crypt.c
#
if `test ! -s ./gram.y`
then
echo "Extracting ./gram.y"
cat > ./gram.y << '\SHAR\EOF\'
/*	SC	A Spreadsheet Calculator
 *		Command and expression parser
 *
 *		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/88, see list of changes.
 *
 *		$Revision: 6.1 $
 */



%{
#include <curses.h>
#include "sc.h"

#define ENULL (struct enode *)0

char *strcpy();
%}

%union {
    int ival;
    double fval;
    struct ent_ptr ent;
    struct enode *enode;
    char *sval;
    struct range_s rval;
}

%type <ent> var
%type <fval> num
%type <rval> range
%type <rval> var_or_range
%type <sval> strarg
%type <enode> e term expr_list
%token <sval> STRING
%token <ival> NUMBER
%token <fval> FNUMBER
%token <rval> RANGE
%token <rval> VAR
%token <sval> WORD
%token <ival> COL
%token S_FORMAT
%token S_LABEL
%token S_LEFTSTRING
%token S_RIGHTSTRING
%token S_GET
%token S_PUT
%token S_MERGE
%token S_LET
%token S_WRITE
%token S_TBL
%token S_COPY
%token S_SHOW
%token S_ERASE
%token S_FILL
%token S_GOTO
%token S_DEFINE
%token S_UNDEFINE
%token S_VALUE
%token S_MDIR
%token S_HIDE
%token S_SET

%token K_FIXED
%token K_SUM
%token K_PROD
%token K_AVG
%token K_STDDEV
%token K_ACOS
%token K_ASIN
%token K_ATAN
%token K_ATAN2
%token K_CEIL
%token K_COS
%token K_EXP
%token K_FABS
%token K_FLOOR
%token K_HYPOT
%token K_LN
%token K_LOG
%token K_PI
%token K_POW
%token K_SIN
%token K_SQRT
%token K_TAN
%token K_DTR
%token K_RTD
%token K_MAX
%token K_MIN
%token K_RND

%token K_PV
%token K_FV
%token K_PMT

%token K_HOUR
%token K_MINUTE
%token K_SECOND
%token K_MONTH
%token K_DAY
%token K_YEAR
%token K_NOW
%token K_DATE
%token K_FMT
%token K_SUBSTR
%token K_STON
%token K_EQS
%token K_EXT
%token K_NVAL
%token K_SVAL
%token K_LOOKUP
%token K_INDEX
%token K_STINDEX
%token K_AUTO
%token K_AUTOCALC
%token K_BYROWS
%token K_BYCOLS
%token K_BYGRAPH
%token K_ITERATIONS
%token K_NUMERIC
%token K_PRESCALE
%token K_EXTFUN
%token K_CELLCUR
%token K_TOPROW
%token K_TBLSTYLE
%token K_TBL
%token K_LATEX
%token K_TEX

%left '?' ':'
%left '|'
%left '&'
%nonassoc '<' '=' '>' '!'
%left '+' '-' '#'
%left '*' '/' '%'
%left '^'

%%
command:	S_LET var_or_range '=' e
				{ let($2.left.vp, $4); }
	|	S_LABEL var_or_range '=' e
				{ slet($2.left.vp, $4, 0); }
	|	S_LEFTSTRING var_or_range '=' e
				{ slet($2.left.vp, $4, -1); }
	|	S_RIGHTSTRING var_or_range '=' e
				{ slet($2.left.vp, $4, 1); }
	|	S_FORMAT COL ':' COL NUMBER NUMBER
				{ doformat($2,$4,$5,$6); }
	|	S_FORMAT COL NUMBER NUMBER
				{ doformat($2,$2,$3,$4); }
	|	S_GET strarg	{  /* This tmp hack is because readfile
				    * recurses back through yyparse. */
				  char *tmp;
				  tmp = $2;
				  readfile (tmp, 1);
				  xfree(tmp);
				}
	|	S_MERGE strarg	{
				  char *tmp;
				  tmp = $2;
				  readfile (tmp, 0);
				  xfree(tmp);
				}
	|	S_MDIR strarg	
				{ if (mdir) xfree(mdir); mdir = $2; }
	|       S_PUT strarg range
				{ (void) writefile($2, ($3.left.vp)->row, 
			 	($3.left.vp)->col, ($3.right.vp)->row,
			 	($3.right.vp)->col);
			 	xfree($2); }
	|	S_PUT strarg	
				{ (void) writefile ($2, 0, 0, maxrow, maxcol);
			 	xfree($2); }
	|       S_WRITE strarg range { (void) printfile($2, ($3.left.vp)->row, 
			 ($3.left.vp)->col, ($3.right.vp)->row,
			 ($3.right.vp)->col);
			 xfree($2); }
	|	S_WRITE strarg	{ (void) printfile ($2, 0, 0, maxrow, maxcol);
			 xfree($2); }
	|       S_TBL strarg range { (void) tblprintfile($2, ($3.left.vp)->row, 
			 ($3.left.vp)->col, ($3.right.vp)->row,
			 ($3.right.vp)->col);
			 xfree($2); }
	|	S_TBL strarg	{ (void)tblprintfile ($2, 0, 0, maxrow, maxcol);
			 xfree($2); }
	|       S_SHOW COL ':' COL
					{ showcol( $2, $4); }
	|       S_SHOW NUMBER ':' NUMBER
					{ showrow( $2, $4); }
 	|	S_HIDE COL
 					{ hide_col( $2 ); }
 	|	S_HIDE NUMBER
 					{ hide_row( $2 ); }
	|	S_COPY range var_or_range 
					{ copy($2.left.vp,$2.right.vp,
					$3.left.vp,$3.right.vp); }
	|	S_ERASE       
					{ eraser(lookat(showsr, showsc),
				        lookat(currow, curcol)); }
	|	S_ERASE var_or_range 
					{ eraser($2.left.vp, $2.right.vp); }
	|	S_VALUE       { valueize_area(showsr, showsc, currow, curcol);
				 modflg++; }
	|	S_VALUE var_or_range { valueize_area(($2.left.vp)->row,
				($2.left.vp)->col,
				($2.right.vp)->row,
				($2.right.vp)->col); modflg++; }
	|	S_FILL num num  { fill(lookat(showsr, showsc),
				      lookat(currow, curcol), $2, $3); }
	|	S_FILL var_or_range num num
				 { fill($2.left.vp, $2.right.vp, $3, $4); }
	|	S_GOTO var_or_range {moveto($2.left.vp->row, $2.left.vp->col);}
	|       S_GOTO num          {num_search($2);}
	|       S_GOTO STRING       {str_search($2);}
	|	S_GOTO              {go_last();}
	|	S_DEFINE strarg       { struct ent_ptr arg1, arg2;
					arg1.vp = lookat(showsr, showsc);
					arg1.vf = 0;
					arg2.vp = lookat(currow, curcol);
					arg2.vf = 0;
					add_range($2, arg1, arg2, 1); }

	|	S_DEFINE strarg range { add_range($2, $3.left, $3.right, 1); }
	|	S_DEFINE strarg var   { add_range($2, $3, $3, 0); }
	|	S_UNDEFINE var_or_range { del_range($2.left.vp, $2.right.vp); }
 	|	S_SET setlist
	|	/* nothing */
	|	error;

term: 		var		{ $$ = new_var('v', $1); }
	|	K_FIXED term	{ $$ = new ('f', ENULL, $2); }
	|       '@' K_SUM '(' var_or_range ')' 
				{ $$ = new_range(REDUCE | '+', $4); }
	|       '@' K_PROD '(' var_or_range ')' 
				{ $$ = new_range (REDUCE | '*', $4); }
	|       '@' K_AVG '(' var_or_range ')' 
				{ $$ = new_range (REDUCE | 'a', $4); }
	|       '@' K_STDDEV '(' var_or_range ')' 
				{ $$ = new_range (REDUCE | 's', $4); }
	|       '@' K_MAX '(' var_or_range ')' 
				{ $$ = new_range (REDUCE | MAX, $4); }
	|	'@' K_MAX '(' e ',' expr_list ')'
				{ $$ = new(LMAX, $6, $4); }
	|       '@' K_MIN '(' var_or_range ')' 
				{ $$ = new_range (REDUCE | MIN, $4); }
	|	'@' K_MIN '(' e ',' expr_list ')'
				{ $$ = new(LMIN, $6, $4); }
	| '@' K_ACOS '(' e ')'
			{ $$ = new(ACOS, ENULL, $4); }
	| '@' K_ASIN '(' e ')'	 { $$ = new(ASIN, ENULL, $4); }
	| '@' K_ATAN '(' e ')'	 { $$ = new(ATAN, ENULL, $4); }
	| '@' K_ATAN2 '(' e ',' e ')' { $$ = new(ATAN2, $4, $6); }
	| '@' K_CEIL '(' e ')'	 { $$ = new(CEIL, ENULL, $4); }
	| '@' K_COS '(' e ')'	 { $$ = new(COS, ENULL, $4); }
	| '@' K_EXP '(' e ')'	 { $$ = new(EXP, ENULL, $4); }
	| '@' K_FABS '(' e ')'	 { $$ = new(FABS, ENULL, $4); }
	| '@' K_FLOOR '(' e ')'	 { $$ = new(FLOOR, ENULL, $4); }
	| '@' K_HYPOT '(' e ',' e ')'	{ $$ = new(HYPOT, $4, $6); }
	| '@' K_LN '(' e ')'	 { $$ = new(LOG, ENULL, $4); }
	| '@' K_LOG '(' e ')'	 { $$ = new(LOG10, ENULL, $4); }
	| '@' K_POW '(' e ',' e ')'	{ $$ = new(POW, $4, $6); }
	| '@' K_SIN '(' e ')'	 { $$ = new(SIN, ENULL, $4); }
	| '@' K_SQRT '(' e ')'	 { $$ = new(SQRT, ENULL, $4); }
	| '@' K_TAN '(' e ')'	 { $$ = new(TAN, ENULL, $4); }
	| '@' K_DTR '(' e ')'	 { $$ = new(DTR, ENULL, $4); }
	| '@' K_RTD '(' e ')'	 { $$ = new(RTD, ENULL, $4); }
	| '@' K_RND '(' e ')'	 { $$ = new(RND, ENULL, $4); }

	| '@' K_PV  '(' e ',' e ',' e ')' { $$ = new(PV,  $4,new(':',$6,$8)); }
 	| '@' K_FV  '(' e ',' e ',' e ')' { $$ = new(FV,  $4,new(':',$6,$8)); }
 	| '@' K_PMT '(' e ',' e ',' e ')' { $$ = new(PMT, $4,new(':',$6,$8)); }
 
	| '@' K_HOUR '(' e ')' 	 { $$ = new(HOUR,ENULL, $4); }
	| '@' K_MINUTE '(' e ')' { $$ = new(MINUTE,ENULL, $4); }
	| '@' K_SECOND '(' e ')' { $$ = new(SECOND,ENULL, $4); }
	| '@' K_MONTH '(' e ')'	 { $$ = new(MONTH,ENULL,$4); }
	| '@' K_DAY '(' e ')'    { $$ = new(DAY, ENULL, $4); }
	| '@' K_YEAR '(' e ')'   { $$ = new(YEAR, ENULL, $4); }
	| '@' K_NOW              { $$ = new(NOW, ENULL, ENULL);}
	| '@' K_STON '(' e ')'   { $$ = new(STON, ENULL, $4); }
	| '@' K_EQS '(' e ',' e ')' { $$ = new (EQS, $4, $6); }
	| '@' K_DATE '(' e ')'	 { $$ = new(DATE, ENULL, $4); }
	| '@' K_FMT  '(' e ',' e ')' { $$ = new(FMT, $4, $6); }
	| '@' K_INDEX  '(' e ',' var_or_range ')'
		 { $$ = new(INDEX, $4, new_range(REDUCE | INDEX, $6)); }
	| '@' K_LOOKUP  '(' e ',' var_or_range ')'
		 { $$ = new(LOOKUP, $4, new_range(REDUCE | LOOKUP, $6)); }
	| '@' K_STINDEX  '(' e ',' var_or_range ')'
		 { $$ = new(STINDEX, $4, new_range(REDUCE | STINDEX, $6)); }
	| '@' K_EXT  '(' e ',' e ')' { $$ = new(EXT, $4, $6); }
	| '@' K_NVAL '(' e ',' e ')' { $$ = new(NVAL, $4, $6); }
	| '@' K_SVAL '(' e ',' e ')' { $$ = new(SVAL, $4, $6); }
	| '@' K_SUBSTR '(' e ',' e ',' e ')'
				 { $$ = new(SUBSTR, $4, new(',', $6, $8)); }
	|	'(' e ')'	 { $$ = $2; }
	|	'+' term	 { $$ = $2; }
	|	'-' term	 { $$ = new ('m', ENULL, $2); }
	|	NUMBER		 { $$ = new_const('k', (double) $1); }
	|	FNUMBER		 { $$ = new_const('k', $1); }
	|	K_PI	{ $$ = new_const('k', (double)3.14159265358979323846); }
	|	STRING	         { $$ = new_str($1); }
	|	'~' term	 { $$ = new ('~', ENULL, $2); }
	|	'!' term	 { $$ = new ('~', ENULL, $2); }
	;

e:		e '+' e		{ $$ = new ('+', $1, $3); }
	|	e '-' e		{ $$ = new ('-', $1, $3); }
	|	e '*' e		{ $$ = new ('*', $1, $3); }
	|	e '/' e		{ $$ = new ('/', $1, $3); }
	|	e '%' e		{ $$ = new ('%', $1, $3); }
	|	e '^' e		{ $$ = new ('^', $1, $3); }
	|	term
	|	e '?' e ':' e	{ $$ = new ('?', $1, new(':', $3, $5)); }
	|	e '<' e		{ $$ = new ('<', $1, $3); }
	|	e '=' e		{ $$ = new ('=', $1, $3); }
	|	e '>' e		{ $$ = new ('>', $1, $3); }
	|	e '&' e		{ $$ = new ('&', $1, $3); }
	|	e '|' e		{ $$ = new ('|', $1, $3); }
	|	e '<' '=' e	{ $$ = new ('~', ENULL, new ('>', $1, $4)); }
	|	e '!' '=' e	{ $$ = new ('~', ENULL, new ('=', $1, $4)); }
	|	e '>' '=' e	{ $$ = new ('~', ENULL, new ('<', $1, $4)); }
	|	e '#' e		{ $$ = new ('#', $1, $3); }
	;

expr_list:	e		{ $$ = new(ELIST, ENULL, $1); }
	|	expr_list ',' e	{ $$ = new(ELIST, $1, $3); }
	;

range:		var ':' var	{ $$.left = $1; $$.right = $3; }
	| 	RANGE		{ $$ = $1; }
	;

var:		COL NUMBER	{ $$.vp = lookat($2 , $1); $$.vf = 0;}
	|	'$' COL NUMBER	{ $$.vp = lookat($3 , $2);
					$$.vf = FIX_COL;}
	|	COL '$' NUMBER	{ $$.vp = lookat($3 , $1);
					$$.vf = FIX_ROW;}
	|	'$' COL '$' NUMBER { $$.vp = lookat($4 , $2);
					$$.vf = FIX_ROW | FIX_COL;}
	|	VAR		{ $$ = $1.left; }
	;

var_or_range:	range		{ $$ = $1; }
	|	var		{ $$.left = $1; $$.right = $1; }
	;

num:		NUMBER		{ $$ = (double) $1; }
	|	FNUMBER		{ $$ = $1; }
	|	'-' num		{ $$ = -$2; }
	|	'+' num		{ $$ = $2; }
	;

strarg:		STRING		{ $$ = $1; }
	|	var		{
				    char *s, *s1;
				    s1 = $1.vp->label;
				    if (!s1)
					s1 = "NULL_STRING";
				    s = xmalloc((unsigned)strlen(s1)+1);
				    (void) strcpy(s, s1);
				    $$ = s;
				}
  	;

setlist :	
	|	setlist setitem
	;

setitem	:	K_AUTO		{ setauto(1); }
	|	K_AUTOCALC	{ setauto(1); }
	|	'~' K_AUTO	{ setauto(0); }
	|	'~' K_AUTOCALC	{ setauto(0); }
	|	'!' K_AUTO	{ setauto(0); }
	|	'!' K_AUTOCALC	{ setauto(0); }
	|	K_BYCOLS	{ setorder(BYCOLS); }
	|	K_BYROWS	{ setorder(BYROWS); }
	|	K_BYGRAPH	{ setorder(BYGRAPH); }
	|	K_NUMERIC	{ numeric = 1; }
	|	'!' K_NUMERIC	{ numeric = 0; }
	|	K_PRESCALE	{ prescale = 0.01; }
	|	'!' K_PRESCALE	{ prescale = 1.0; }
	|	K_EXTFUN	{ extfunc = 1; }
	|	'!' K_EXTFUN	{ extfunc = 0; }
	|	K_CELLCUR	{ showcell = 1; }
	|	'!' K_CELLCUR	{ showcell = 0; }
	|	K_TOPROW	{ showtop = 1; }
	|	'!' K_TOPROW	{ showtop = 0; }
	|	K_ITERATIONS '=' NUMBER	{ setiterations($3); }
	|	K_TBLSTYLE '=' NUMBER	{ tbl_style = $3; }
	|	K_TBLSTYLE '=' K_TBL	{ tbl_style = TBL; }
	|	K_TBLSTYLE '=' K_LATEX	{ tbl_style = LATEX; }
	|	K_TBLSTYLE '=' K_TEX	{ tbl_style = TEX; }
  	;
\SHAR\EOF\
else
  echo "will not over write ./gram.y"
fi
if [ `wc -c ./gram.y | awk '{printf $1}'` -ne 11263 ]
then
echo `wc -c ./gram.y | awk '{print "Got " $1 ", Expected " 11263}'`
fi
if `test ! -s ./interp.c`
then
echo "Extracting ./interp.c"
cat > ./interp.c << '\SHAR\EOF\'
/*	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: 6.1 $
 */

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

extern int errno;		/* set by math functions */
#ifdef BSD42
#include <strings.h>
#include <sys/time.h>
#ifndef strchr
#define strchr rindex
#endif
#else
#include <time.h>
#ifndef SYSIII
#include <string.h>
#endif
#endif

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

#if defined(BSD42) || defined(BSD43)
char *re_comp();
#endif
#if defined(SYSV2) || defined(SYSV3)
char *regcmp();
char *regex();
#endif

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

/* Use this structure to save the the last 'g' command */

struct go_save {
	int g_type;
	double g_n;
	char *g_s;
	int  g_row;
	int  g_col;
} gs;

/* g_type can be: */

#define G_NONE 0			/* Starting value - must be 0*/
#define G_NUM 1
#define G_STR 2
#define G_CELL 3

extern FILE *popen();

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

#define PI (double)3.14159265358979323846
#define dtr(x) ((x)*(PI/(double)180.0))
#define rtd(x) ((x)*(180.0/(double)PI))

double finfunc(fun,v1,v2,v3)
int fun;
double v1,v2,v3;
{
 	double answer,p;
 
 	p = fn2_eval(pow, 1 + v2, v3);
 
 	switch(fun)
 	{
 	case PV:
 		answer = v1 * (1 - 1/p) / v2;
 		break;
 	case FV:
 		answer = v1 * (p - 1) / v2;
 		break;
 	case PMT:
 		answer = v1 * v2 / (1 - 1/p);
 		break;
	}
	return(answer);
}

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

    x = (int) val;
    r = minr; c = minc;
    p = 0;
    if ( minr == maxr ) { /* look along the row */
	c = minc + x - 1;
	if (c <= maxc && c >=minc)
	    p = tbl[r][c];
    } else if ( minc == maxc ) { /* look down the column */
	r = minr + x - 1;
	if (r <= maxr && r >=minr)
	    p = tbl[r][c];
    } else {
	error ("range specified to @stindex");
	return(0);
    }
    if (p && p->label) {
	pr = xmalloc((unsigned)(strlen(p->label)+1));
	(void)strcpy(pr, p->label);
	return (pr);
     } else
	return(0);
}

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

    x = (int) val;
    v = 0;
    r = minr; c = minc;
    if ( minr == maxr ) { /* look along the row */
	c = minc + x - 1;
	if (c <= maxc && c >=minc 
		&& (p = tbl[r][c] ) && p->flags&is_valid )
					return p->v;
	}
    else if ( minc == maxc ){ /* look down the column */
	r = minr + x - 1;
	if (r <= maxr && r >=minr 
		&& (p = tbl[r][c] ) && p->flags&is_valid )
					return p->v;
	}
    else error(" range specified to @index");
    return v;
}

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

    v = 0;
    r = minr; c = minc;
    if ( minr == maxr ) { /* look along the row */
	for ( c = minc; c <= maxc; c++) {
		if ( (p = tbl[r][c] ) && p->flags&is_valid ) {
			if(p->v <= val) {
				p = tbl[r+1][c];
				if ( p && p->flags&is_valid)
					v = p->v;
				}
			else return v;
			}
		}
	}
    else if ( minc == maxc ){ /* look down the column */
	for ( r = minr; r <= maxr; r++) {
		if ( (p = tbl[r][c] ) && p->flags&is_valid ) {
			if(p->v <= val) {
				p = tbl[r][c+1];
				if ( p && p->flags&is_valid)
					v = p->v;
				}
			else return v;
			}
		}
	}
    else error(" range specified to @lookup");
    return v;
}

double
dolookups(s, minr, minc, maxr, maxc)
char *s;
int minr, minc, maxr, maxc;
{
    double v;
    register r,c;
    register struct ent *p;

    v = 0;
    r = minr; c = minc;
    if ( minr == maxr ) { /* look along the row */
	for ( c = minc; c <= maxc; c++) {
	    if ( (p = tbl[r][c] ) && p->label) {
		if(strcmp(s,p->label) == 0) {
		    p = tbl[r+1][c];
		    xfree(s);
		    if ( p && p->flags & is_valid)
			return(p->v);
		}
	    }
	}
    } else if ( minc == maxc ) { /* look down the column */
	for ( r = minr; r <= maxr; r++) {
	    if ( (p = tbl[r][c] ) && p->label) {
		if(strcmp(s,p->label) == 0) {
		    p = tbl[r][c+1];
		    xfree(s);
		    if ( p && p->flags & is_valid)
			return(p->v);
		}
	    }
	}
    } else error(" range specified to @lookup");
    xfree(s);
    return v;
}

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

    v = 0;
    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++)
	    if ((p = tbl[r][c]) && p->flags&is_valid)
		v += p->v;
    return v;
}

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

    v = 1;
    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++)
	    if ((p = tbl[r][c]) && p->flags&is_valid)
		v *= p->v;
    return v;
}

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

    v = 0;
    count = 0;
    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++)
	    if ((p = tbl[r][c]) && p->flags&is_valid) {
		v += p->v;
		count++;
	    }

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

    return (v / (double)count);
}

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

    n = 0;
    lp = 0;
    rp = 0;
    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++)
	    if ((p = tbl[r][c]) && p->flags&is_valid) {
		v = p->v;
		lp += v*v;
		rp += v;
		n++;
	    }

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

double
domax(minr, minc, maxr, maxc)
int minr, minc, maxr, maxc;
{
    double v;
    register r,c,count;
    register struct ent *p;

    count = 0;
    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++)
	    if ((p = tbl[r][c]) && p->flags&is_valid) {
		if (!count) {
		    v = p->v;
		    count++;
		} else if (p->v > v)
		    v = p->v;
	    }

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

    return (v);
}

double
domin(minr, minc, maxr, maxc)
int minr, minc, maxr, maxc;
{
    double v;
    register r,c,count;
    register struct ent *p;

    count = 0;
    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++)
	    if ((p = tbl[r][c]) && p->flags&is_valid) {
		if (!count) {
		    v = p->v;
		    count++;
		} else if (p->v < v)
		    v = p->v;
	    }

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

    return (v);
}

double
dotime(which, when)
int which;
double when;
{
	long time();

	static long t_cache;
	static struct tm *tp;
	long tloc;

	if (which == NOW) 
	    return (double)time((long *)0);

	tloc = (long)when;

	if (tloc != t_cache) {
	    tp = localtime(&tloc);
	    tp->tm_mon += 1;
	    tp->tm_year += 1900;
	    t_cache = tloc;
	}

	switch (which) {
		case HOUR: return((double)(tp->tm_hour));
		case MINUTE: return((double)(tp->tm_min));
		case SECOND: return((double)(tp->tm_sec));
		case MONTH: return((double)(tp->tm_mon));
		case DAY: return((double)(tp->tm_mday));
		case YEAR: return((double)(tp->tm_year));
	}
	/* Safety net */
	return (0.0);
}

double
doston(s)
char *s;
{
    char *strtof();
    double v;

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

    (void)strtof(s, &v);
    xfree(s);
    return(v);
}

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

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

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

    if (s1)
    	xfree(s1);

    if (s2)
    	xfree(s2);

    return(v);
}


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

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

    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 */
    {
	ep = tbl [row] [col];
    }

    xfree (colstr);
    return (ep);
}


/*
 * 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 (colstr, rowdoub)
    char *colstr;
    double rowdoub;
{
    struct ent *ep;

    return (((ep = getent (colstr, rowdoub)) && ((ep -> flags) & is_valid)) ?
	    (ep -> v) : 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(ep)
struct enode *ep;
{
	register int count = 0;
	register double maxval = 0; /* Assignment to shut up lint */
	register struct enode *p;
	register double v;

	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 0.0;
}

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

	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 0.0;
}

double 
eval(e)
register struct enode *e;
{

    if (e==0) return 0;
    switch (e->op) {
	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 '/':     	return (eval(e->e.o.left) / eval(e->e.o.right));
	case '%':     {	double num, denom;
			num = floor(eval(e->e.o.left));
			denom = floor(eval (e->e.o.right));
			return denom ? num - floor(num/denom)*denom : 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 '=':	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 '|':	return (eval(e->e.o.left) || eval(e->e.o.right));
	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.right));
	case 'f':	return (eval(e->e.o.right));
	case '~':	return (eval(e->e.o.right) == 0.0);
	case 'k':	return (e->e.k);
	case 'v':	return (e->e.v.vp->v);
	case INDEX:
	case LOOKUP:
		    {	register r,c;
		register maxr, maxc;
		register minr, minc;
		maxr = e->e.o.right->e.r.right.vp -> row;
		maxc = e->e.o.right->e.r.right.vp -> col;
		minr = e->e.o.right->e.r.left.vp -> row;
		minc = e->e.o.right->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:
		if (etype(e->e.o.left) == NUM)
	            return dolookupn(eval(e->e.o.left), minr, minc, maxr, maxc);
		else
	            return dolookups(seval(e->e.o.left),minr, minc, maxr, maxc);
		case INDEX:
	        return doindex(eval(e->e.o.left), minr, minc, maxr, maxc);
		}
		}
	case REDUCE | '+':
 	case REDUCE | '*':
 	case REDUCE | 'a':
 	case REDUCE | 's':
	case REDUCE | MAX:
	case REDUCE | MIN:
	    {	register r,c;
		register maxr, maxc;
		register 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 | '+': return dosum(minr, minc, maxr, maxc);
 	            case REDUCE | '*': return doprod(minr, minc, maxr, maxc);
 	            case REDUCE | 'a': return doavg(minr, minc, maxr, maxc);
 	            case REDUCE | 's': return dostddev(minr, minc, maxr, maxc);
 	            case REDUCE | MAX: return domax(minr, minc, maxr, maxc);
 	            case REDUCE | MIN: return domin(minr, minc, maxr, maxc);
		}
	    }
	case ACOS:	 return (fn1_eval( acos, eval(e->e.o.right)));
	case ASIN:	 return (fn1_eval( asin, eval(e->e.o.right)));
	case ATAN:	 return (fn1_eval( atan, eval(e->e.o.right)));
	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.right)));
	case COS:	 return (fn1_eval( cos, eval(e->e.o.right)));
	case EXP:	 return (fn1_eval( exp, eval(e->e.o.right)));
	case FABS:	 return (fn1_eval( fabs, eval(e->e.o.right)));
	case FLOOR:	 return (fn1_eval( floor, eval(e->e.o.right)));
	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.right)));
	case LOG10:	 return (fn1_eval( log10, eval(e->e.o.right)));
	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.right)));
	case SQRT:	 return (fn1_eval( sqrt, eval(e->e.o.right)));
	case TAN:	 return (fn1_eval( tan, eval(e->e.o.right)));
	case DTR:	 return (dtr(eval(e->e.o.right)));
	case RTD:	 return (rtd(eval(e->e.o.right)));
	case RND:	 {
			    double temp;
			    temp = eval(e->e.o.right);
			    return(temp-floor(temp) < 0.5 ?
					     floor(temp) : ceil(temp));
			 }
	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.right)));
	case MINUTE:	 return (dotime(MINUTE, eval(e->e.o.right)));
	case SECOND:	 return (dotime(SECOND, eval(e->e.o.right)));
	case MONTH:	 return (dotime(MONTH, eval(e->e.o.right)));
	case DAY:	 return (dotime(DAY, eval(e->e.o.right)));
	case YEAR:	 return (dotime(YEAR, eval(e->e.o.right)));
	case NOW:	 return (dotime(NOW, (double)0.0));
	case STON:	 return (doston(seval(e->e.o.right)));
	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)));
	default:	 error ("Illegal numeric expression");
			 exprerr = 1;
			 return((double)0.0);
    }
#ifdef sequent
    return((double)0.0);	/* Quiet a questionable compiler complaint */
#endif
}

#ifdef SIGVOID
void
#endif
eval_fpe() /* Trap for FPE errors in eval */
{
	longjmp(fpe_save, 1);
}

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

	return res;
}

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

	return res;
}

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

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

    if (!s1 && !s2)
	return(0);
    arg1 = s1 ? s1 : "";
    arg2 = s2 ? s2 : "";
    p = xmalloc((unsigned)(strlen(arg1)+strlen(arg2)+1));
    (void) strcpy(p, arg1);
    (void) strcat(p, arg2);
    if (s1)
        xfree(s1);
    if (s2)
        xfree(s2);
    return(p);
}

char *
dodate(tloc)
long tloc;
{
    char *tp;
    char *p;

    tp = ctime(&tloc);
    tp[24] = 0;
    p = xmalloc((unsigned)25);
    (void) strcpy(p, tp);
    return(p);
}


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

    if (!fmtstr)
	return(0);
    (void)sprintf(buff, fmtstr, v);
    p = xmalloc((unsigned)(strlen(buff)+1));
    (void) strcpy(p, buff);
    xfree(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 prevstr, which is set appropriately first unless external
 * functions are disabled, in which case the previous value is used.  The
 * handling of prevstr and freeing of command is tricky.  Returning an
 * allocated string in all cases, even if null, insures cell expressions are
 * written to files, etc.
 */

#ifdef VMS
char *
doext(command, value)
char *command;
double value;
{
    error("Warning: External functions unavailable on VMS");
    if (command)
	xfree(command);
    return (strcpy (xmalloc((unsigned) 1), "\0"));
}

#else /* VMS */

char *
doext (command, value)
char   *command;
double value;
{
    static char *prevstr = 0;	/* previous result */
    char buff[1024];		/* command line/return, not permanently alloc */

    if (!prevstr) {
	prevstr = xmalloc((unsigned)1);
	*prevstr = 0;
    }
    if (!extfunc)    {
	error ("Warning: external functions disabled; using %s value",
		prevstr ? "previous" : "null");

	if (command) xfree (command);
    } else {
	if (prevstr) xfree (prevstr);		/* no longer needed */
	prevstr = 0;

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

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

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

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

		    error ("");				/* erase notice */
		    buff[1023] = 0;

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

		    (void) strcpy (prevstr = 
			 xmalloc ((unsigned) (strlen (buff) + 1)), buff);
			 /* save alloc'd copy */
		}
		(void) pclose (pp);

	    } /* else */
	} /* else */
    } /* else */
    return (strcpy (xmalloc ((unsigned) (strlen (prevstr) + 1)), prevstr));
}

#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 (colstr, rowdoub)
    char *colstr;
    double rowdoub;
{
    struct ent *ep;
    char *label;

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


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

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

    if (!s)
	return(0);

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

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

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

    if (se==0) return 0;
    switch (se->op) {
	case O_SCONST: p = xmalloc((unsigned)(strlen(se->e.s)+1));
		     (void) strcpy(p, se->e.s);
		     return(p);
	case O_VAR:    {
			struct ent *ep;
			ep = se->e.v.vp;

			if (!ep->label)
			    return(0);
			p = xmalloc((unsigned)(strlen(ep->label)+1));
			(void) strcpy(p, ep->label);
			return(p);
		     }
	case '#':    return(docat(seval(se->e.o.left), seval(se->e.o.right)));
	case 'f':    return(seval(se->e.o.right));
	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((long)(eval(se->e.o.right))));
	case FMT:    return(dofmt(seval(se->e.o.left), eval(se->e.o.right)));
 	case STINDEX:
 		{	register r,c;
 		register maxr, maxc;
 		register minr, minc;
 		maxr = se->e.o.right->e.r.right.vp -> row;
 		maxc = se->e.o.right->e.r.right.vp -> col;
 		minr = se->e.o.right->e.r.left.vp -> row;
 		minc = se->e.o.right->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(eval(se->e.o.left), minr, minc, maxr, maxc);
		}
	case EXT:    return(doext(seval(se->e.o.left), eval(se->e.o.right)));
	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));
	default:
		     error ("Illegal string expression");
		     exprerr = 1;
		     return(0);
	}
}

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

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

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

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

    (void) signal(SIGFPE, eval_fpe);
    if(calc_order == BYROWS ) {
    for (i=0; i<=maxrow; i++)
  	for (j=0; j<=maxcol; j++)
 	    if ((p=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=tbl[i][j]) && p->expr) RealEvalOne(p,i,j, &chgct);
    }
    else error("Internal error calc_order");
 
    (void) signal(SIGFPE, quit);
    return(chgct);
}

RealEvalOne(p, i , j, chgct)
register struct ent *p;
int i, j, *chgct;
{
	if (p->flags & is_strexpr) {
	    char *v;
	    if (setjmp(fpe_save)) {
		error("Floating point exception %s", v_name( i, j));
		v = "";
	    } else {
		v = seval(p->expr);
	    }
	    if (!v && !p->label) /* Everything's fine */
		return;
	    if (!p->label || !v || strcmp(v, p->label) != 0) {
		(*chgct)++;
		p->flags |= is_changed;
	    }
	    if(p->label)
		xfree(p->label);
	    p->label = v;
	} else {
	    double v;
	    if (setjmp(fpe_save)) {
		error("Floating point exception %s", v_name( i, j));
		v = 0.0;
	    } else {
		v = eval (p->expr);
	    }
	    if (v != p->v) {
		p->v = v; (*chgct)++;
		p->flags |= is_changed|is_valid;
	    }
	}
}

struct enode *
new(op, a1, a2)
struct enode *a1, *a2;
{
    register struct enode *p;
    p = (struct enode *) xmalloc ((unsigned)sizeof (struct enode));
    p->op = op;
    p->e.o.left = a1;
    p->e.o.right = a2;
    return p;
}

struct enode *
new_var(op, a1)
struct ent_ptr a1;
{
    register struct enode *p;
    p = (struct enode *) xmalloc ((unsigned)sizeof (struct enode));
    p->op = op;
    p->e.v = a1;
    return p;
}

struct enode *
new_range(op, a1)
struct range_s a1;
{
    register struct enode *p;
    p = (struct enode *) xmalloc ((unsigned)sizeof (struct enode));
    p->op = op;
    p->e.r = a1;
    return p;
}

struct enode *
new_const(op, a1)
double a1;
{
    register struct enode *p;
    p = (struct enode *) xmalloc ((unsigned)sizeof (struct enode));
    p->op = op;
    p->e.k = a1;
    return p;
}

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

    p = (struct enode *) xmalloc ((unsigned)sizeof(struct enode));
    p->op = O_SCONST;
    p->e.s = s;
    return(p);
}

copy(dv1, dv2, v1, v2)
struct ent *dv1, *dv2, *v1, *v2;
{
    int minsr, minsc;
    int maxsr, maxsc;
    int mindr, mindc;
    int maxdr, maxdc;
    int vr, vc;
    int r, c;

    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;
    maxsr = v2->row;
    maxsc = v2->col;
    minsr = v1->row;
    minsc = v1->col;
    if (minsr>maxsr) r = maxsr, maxsr = minsr, minsr = r;
    if (minsc>maxsc) c = maxsc, maxsc = minsc, minsc = c;
    if (maxdr >= MAXROWS  || 
           maxdc >= MAXCOLS) {
	error ("The table can't be any bigger");
	return;
    }
    erase_area(mindr, mindc, maxdr, maxdc);
    if (minsr == maxsr && minsc == maxsc) {
	/* Source is a single cell */
	for(vr = mindr; vr <= maxdr; vr++)
	    for (vc = mindc; vc <= maxdc; vc++)
		copyrtv(vr, vc, minsr, minsc, maxsr, maxsc);
    } else if (minsr == maxsr) {
	/* Source is a single row */
	for (vr = mindr; vr <= maxdr; vr++)
	    copyrtv(vr, mindc, minsr, minsc, maxsr, maxsc);
    } else if (minsc == maxsc) {
	/* Source is a single column */
	for (vc = mindc; vc <= maxdc; vc++)
	    copyrtv(mindr, vc, minsr, minsc, maxsr, maxsc);
    } else {
	/* Everything else */
	copyrtv(mindr, mindc, minsr, minsc, maxsr, maxsc);
    }
    sync_refs();
}

copyrtv(vr, vc, minsr, minsc, maxsr, maxsc)
int vr, vc, minsr, minsc, maxsr, maxsc;
{
    register struct ent *p;
    register struct ent *n;
    register int sr, sc;
    register int dr, dc;

    for (dr=vr, sr=minsr; sr<=maxsr; sr++, dr++)
	for (dc=vc, sc=minsc; sc<=maxsc; sc++, dc++) {
	    n = lookat (dr, dc);
	    (void) clearent(n);
	    if (p = tbl[sr][sc])
		copyent( n, p, dr - sr, dc - sc);
	}
}

eraser(v1, v2)
struct ent *v1, *v2;
{
	FullUpdate++;
	flush_saved();
	erase_area(v1->row, v1->col, v2->row, v2->col);
	sync_refs();
}

/* Goto subroutines */

g_free()
{
    switch (gs.g_type) {
    case G_STR: xfree(gs.g_s); break;
    default: break;
    }
    gs.g_type = G_NONE;
}

go_last()
{
    switch (gs.g_type) {
    case G_NONE:
		error("Nothing to repeat"); break;
    case G_NUM:
		num_search(gs.g_n);
		break;
    case  G_CELL:
		moveto(gs.g_row, gs.g_col);
	    	break;
    case  G_STR: 
		gs.g_type = G_NONE;	/* Don't free the string */
   	    	str_search(gs.g_s); 
	   	break;

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

moveto(row, col)
int row, col;
{
    currow = row;
    curcol = col;
    g_free();
    gs.g_type = G_CELL;
    gs.g_row = currow;
    gs.g_col = curcol;
}

num_search(n)
double n;
{
    register struct ent *p;
    register int r,c;

    g_free();
    gs.g_type = G_NUM;
    gs.g_n = n;

    r = currow;
    c = curcol;
    do {
	if (c < maxcol)
	    c++;
	else {
	    if (r < maxrow) {
		while(++r < maxrow && row_hidden[r]) /* */;
		c = 0;
	    } else {
		r = 0;
		c = 0;
	    }
	}
	if (r == currow && c == curcol) {
	    error("Number not found");
	    return;
	}
	p = tbl[r][c];
    } while(col_hidden[c] || !p || p && (!(p->flags & is_valid) 
                                        || (p->flags&is_valid) && p->v != n));
    currow = r;
    curcol = c;
}


str_search(s)
char *s;
{
    register struct ent *p;
    register int r,c;
    char *tmp;

#if defined(BSD42) || defined(BSD43)
    if ((tmp = re_comp(s)) != (char *)0) {
	xfree(s);
	error(tmp);
	return;
    }
#endif
#if defined(SYSV2) || defined(SYSV3)
    if ((tmp = regcmp(s, (char *)0)) == (char *)0) {
	xfree(s);
	error("Invalid search string");
	return;
    }
#endif
    g_free();
    gs.g_type = G_STR;
    gs.g_s = s;
    r = currow;
    c = curcol;
    do {
	if (c < maxcol)
	    c++;
	else {
	    if (r < maxrow) {
		while(++r < maxrow && row_hidden[r]) /* */;
		c = 0;
	    } else {
		r = 0;
		c = 0;
	    }
	}
	if (r == currow && c == curcol) {
	    error("String not found");
#if defined(SYSV2) || defined(SYSV3)
	    free(tmp);
#endif
	    return;
	}
	p = tbl[r][c];
    } while(col_hidden[c] || !p || p && (!(p->label) 
#if defined(BSD42) || defined(BSD43)
		  			|| (re_exec(p->label) == 0)));
#else
#if defined(SYSV2) || defined(SYSV3)
                                       || (regex(tmp, p->label) == (char *)0)));
#else
                                       || (strcmp(s, p->label) != 0)));
#endif
#endif
    currow = r;
    curcol = c;
#if defined(SYSV2) || defined(SYSV3)
    free(tmp);
#endif
}

fill (v1, v2, start, inc)
struct ent *v1, *v2;
double start, inc;
{
    register 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;
    if (maxr >= MAXROWS) maxr = MAXROWS-1;
    if (maxc >= MAXCOLS) maxc = MAXCOLS-1;
    if (minr < 0) minr = 0;
    if (minr < 0) minr = 0;

    FullUpdate++;
    if( calc_order == BYROWS ) {
    for (r = minr; r<=maxr; r++)
	for (c = minc; c<=maxc; c++) {
	    n = lookat (r, c);
	    (void) clearent(n);
	    n->v = start;
	    start += inc;
	    n->flags |= (is_changed|is_valid);
	}
    }
    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);
	}
    }
    else error(" Internal error calc_order");
}

let (v, e)
struct ent *v;
struct enode *e;
{
    double val;

    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 = 0.0;
    } else {
	val = eval(e);
    }
    (void) signal(SIGFPE, quit);
    if (exprerr) {
	efree(e);
	return;
    }
    if (constant(e)) {
	if (!loading)
	    v->v = val * prescale;
	else
	    v->v = val;
	if (!(v->flags & is_strexpr)) {
            efree (v->expr);
	    v->expr = 0;
	}
	efree(e);
        v->flags |= (is_changed|is_valid);
        changed++;
        modflg++;
	return;
    }
    efree (v->expr);
    v->expr = e;
    v->flags |= (is_changed|is_valid);
    v->flags &= ~is_strexpr;
    changed++;
    modflg++;
}

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

    exprerr = 0;
    (void) signal(SIGFPE, eval_fpe);
    if (setjmp(fpe_save)) {
	error ("Floating point exception in cell %s", v_name(v->row, v->col));
	p = "";
    } else {
	p = seval(se);
    }
    (void) signal(SIGFPE, quit);
    if (exprerr) {
	efree(se);
	return;
    }
    if (constant(se)) {
	label(v, p, flushdir);
	if (p)
	    xfree(p);
	efree(se);
	if (v->flags & is_strexpr) {
            efree (v->expr);
	    v->expr = 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;
    else v->flags &= ~is_leftflush;
    FullUpdate++;
    changed++;
    modflg++;
}

hide_row(arg)
int arg;
{
    if (arg < 0) {
	error("Invalid Range");
	return;
    }
    if (arg > MAXROWS-2) {
	error("You can't hide the last row");
	return;
    }
    FullUpdate++;
    row_hidden[arg] = 1;
}

hide_col(arg)
int arg;
{
    if (arg < 0) {
	error("Invalid Range");
	return;
    }
    if (arg > MAXCOLS-2) {
	error("You can't hide the last col");
	return;
    }
    FullUpdate++;
    col_hidden[arg] = 1;
}

clearent (v)
struct ent *v;
{
    if (!v)
	return;
    label(v,"",-1);
    v->v = 0;
    if (v->expr)
	efree(v->expr);
    v->expr = 0;
    v->flags |= (is_changed);
    v->flags &= ~(is_valid);
    changed++;
    modflg++;
}

/*
 * Say if an expression is a constant (return 1) or not.
 */

constant (e)
    register struct enode *e;
{
    return ((e == 0)
	 || ((e -> op) == O_CONST)
	 || ((e -> op) == O_SCONST)
	 || (((e -> op) != O_VAR)
	  && (((e -> op) & REDUCE) != 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)));
}

efree (e)
register struct enode *e;
{
    if (e) {
	if (e->op != O_VAR && e->op !=O_CONST && e->op != O_SCONST
		&& (e->op & REDUCE) != REDUCE) {
	    efree(e->e.o.left);
	    efree(e->e.o.right);
	}
	if (e->op == O_SCONST && e->e.s)
	    xfree(e->e.s);
	xfree ((char *)e);
    }
}

label (v, s, flushdir)
register struct ent *v;
register char *s;
{
    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) xfree((char *)(v->label));
	if (s && s[0]) {
	    v->label = xmalloc ((unsigned)(strlen(s)+1));
	    (void) strcpy (v->label, s);
	} else
	    v->label = 0;
	if (flushdir<0) v->flags |= is_leftflush;
	else v->flags &= ~is_leftflush;
	FullUpdate++;
	modflg++;
    }
}

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

	if (!v.vp) (void)sprintf (line+linelim,"VAR?");
	else if (r = find_range((char *)0, 0, v.vp, v.vp))
	    (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(col)
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
decompile_list(p)
struct enode *p;
{
	if (!p) return;
	decompile_list(p->e.o.left);	/* depth first */
        decompile(p->e.o.right, 0);
	line[linelim++] = ',';
}

decompile(e, priority)
register struct enode *e; {
    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 '<': 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.right, 30);
			break;
	case 'm':	line[linelim++] = '-';
			decompile (e->e.o.right, 30);
			break;
	case '~':	line[linelim++] = '~';
			decompile (e->e.o.right, 30);
			break;
	case 'v':	decodev (e->e.v);
			break;
	case 'k':	(void)sprintf (line+linelim,"%.15g",e->e.k);
			linelim += strlen (line+linelim);
			break;
	case '$':	(void)sprintf (line+linelim, "\"%s\"", e->e.s);
			linelim += strlen(line+linelim);
			break;

	case REDUCE | '+': range_arg( "@sum(", e); break;
	case REDUCE | '*': range_arg( "@prod(", e); break;
	case REDUCE | 'a': range_arg( "@avg(", e); break;
	case REDUCE | 's': range_arg( "@stddev(", e); break;
	case REDUCE | MAX: range_arg( "@max(", e); break;
	case REDUCE | MIN: range_arg( "@min(", 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 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 DATE:	one_arg( "@date(", e); break;
	case STON:	one_arg( "@ston(", e); break;
	case FMT:	two_arg( "@fmt(", e); break;
	case EQS:	two_arg( "@eqs(", e); break;
	case NOW:	for ( s = "@now"; line[linelim++] = *s++;);
			linelim--;
			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;

	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++] = '?';
}

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

list_arg(s, e)
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] = ')';
}

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

two_arg(s,e)
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++] = ')';
}

three_arg(s,e)
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++] = ')';
}

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

    for (; line[linelim++] = *s++;);
    linelim--;
    if (r = find_range((char *)0, 0, e->e.r.left.vp,
			     e->e.r.right.vp)) {
	(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++] = ')';
}

editv (row, col)
int row, 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_strexpr || p->expr == 0) {
	(void)sprintf (line+linelim, "%.15g", p->v);
	linelim += strlen (line+linelim);
    } else {
        editexp(row,col);
    }
}

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

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

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

    p = lookat (row, col);
    (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;
    }
}
\SHAR\EOF\
else
  echo "will not over write ./interp.c"
fi
if [ `wc -c ./interp.c | awk '{printf $1}'` -ne 40555 ]
then
echo `wc -c ./interp.c | awk '{print "Got " $1 ", Expected " 40555}'`
fi
if `test ! -s ./crypt.c`
then
echo "Extracting ./crypt.c"
cat > ./crypt.c << '\SHAR\EOF\'
/*
 * Encryption utilites
 * Bradley Williams	
 * {allegra,ihnp4,uiucdcs,ctvax}!convex!williams
 * $Revision: 6.1 $
 */

#include <stdio.h>
#include <curses.h>

#if defined(BSD42) || defined(BSD43)
#include <sys/file.h>
#else
#include <fcntl.h>
#endif

#include "sc.h"

char        *strcpy();

#ifdef SYSV3
void exit();
#endif

int         Crypt = 0;

creadfile (save, eraseflg)
char *save;
int  eraseflg;
{
    register FILE *f;
    int pipefd[2];
    int fildes;
    int pid;

    if (eraseflg && strcmp(save, curfile) && modcheck(" first")) return;

    fildes = open (save, O_RDONLY, 0);
    if (fildes < 0)
    {
	error ("Can't read file \"%s\"", save);
	return;
    }

    if (eraseflg) erasedb ();

    if (pipe(pipefd) < 0) {
	error("Can't make pipe to child");
	return;
    }

    deraw();
    if ((pid=fork()) == 0)			  /* if child  */
    {
	(void) close (0);		  /* close stdin */
	(void) close (1);		  /* close stdout */
	(void) close (pipefd[0]);	  /* close pipe input */
	(void) dup (fildes);		  /* standard in from file */
	(void) dup (pipefd[1]);		  /* connect to pipe */
	(void) fprintf (stderr, " ");
	(void) execl ("/bin/sh", "sh", "-c", "crypt", 0);
	exit (-127);
    }
    else				  /* else parent */
    {
	(void) close (fildes);
	(void) close (pipefd[1]);	  /* close pipe output */
	f = fdopen (pipefd[0], "r");
	if (f == 0)
	{
	    (void) kill (pid, -9);
	    error ("Can't fdopen file \"%s\"", save);
	    (void) close (pipefd[0]);
	    return;
	}
    }

    loading++;
    while (fgets(line,sizeof line,f)) {
	linelim = 0;
	if (line[0] != '#') (void) yyparse ();
    }
    --loading;
    (void) fclose (f);
    (void) close (pipefd[0]);
    while (pid != wait(&fildes)) /**/;
    goraw();
    linelim = -1;
    modflg++;
    if (eraseflg) {
	(void) strcpy (curfile, save);
	modflg = 0;
    }
    EvalAll();
}

cwritefile (fname, r0, c0, rn, cn)
char *fname;
int r0, c0, rn, cn;
{
    register FILE *f;
    int pipefd[2];
    int fildes;
    int pid;
    char save[1024];
    char *fn;


    if (*fname == 0) fname = &curfile[0];

    fn = fname;
    while (*fn && (*fn == ' '))  /* Skip leading blanks */
	fn++;

    if ( *fn == '|' ) {
	error ("Can't have encrypted pipe");
	return(-1);
	}

    (void) strcpy(save,fname);

    fildes = open (save, O_WRONLY|O_CREAT, 0600);
    if (fildes < 0)
    {
	error ("Can't create file \"%s\"", save);
	return(-1);
    }

    if (pipe (pipefd) < 0) {
	error ("Can't make pipe to child\n");
	return(-1);
    }

    deraw();
    if ((pid=fork()) == 0)			  /* if child  */
    {
	(void) close (0);			  /* close stdin */
	(void) close (1);			  /* close stdout */
	(void) close (pipefd[1]);		  /* close pipe output */
	(void) dup (pipefd[0]);			  /* connect to pipe input */
	(void) dup (fildes);			  /* standard out to file */
	(void) fprintf (stderr, " ");
	(void) execl ("/bin/sh", "sh", "-c", "crypt", 0);
	exit (-127);
    }
    else				  /* else parent */
    {
	(void) close (fildes);
	(void) close (pipefd[0]);		  /* close pipe input */
	f = fdopen (pipefd[1], "w");
	if (f == 0)
	{
	    (void) kill (pid, -9);
	    error ("Can't fdopen file \"%s\"", save);
	    (void) close (pipefd[1]);
	    return(-1);
	}
    }

    write_fd(f, r0, c0, rn, cn);

    (void) fclose (f);
    (void) close (pipefd[1]);
    while (pid != wait(&fildes)) /**/;
    (void) strcpy(curfile,save);

    modflg = 0;
    error ("File \"%s\" written", curfile);
    goraw();
    return(0);
}

\SHAR\EOF\
else
  echo "will not over write ./crypt.c"
fi
if [ `wc -c ./crypt.c | awk '{printf $1}'` -ne 3437 ]
then
echo `wc -c ./crypt.c | awk '{print "Got " $1 ", Expected " 3437}'`
fi
echo "Finished archive 3 of 4"
# if you want to concatenate archives, remove anything after this line
exit

-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.