/*	Copyright (C) 1990 Free Software Foundation, Inc.

This file is part of Oleo, the GNU Spreadsheet.

Oleo is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.

Oleo is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Oleo; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "funcdef.h"

#include <stdio.h>
#include <ctype.h>
#include <signal.h>

#include "sysdef.h"

#include "global.h"
#include "cell.h"
#include "eval.h"

#ifdef REVERSE
#define MIN_R MIN_COL
#define MAX_R MAX_COL
#define MIN_C MIN_ROW
#define MAX_C MAX_ROW
#else
#define MIN_R MIN_ROW
#define MAX_R MAX_ROW
#define MIN_C MIN_COL
#define MAX_C MAX_COL
#endif

extern VOIDSTAR hash_new();
extern VOIDSTAR hash_find();
extern char *hash_insert();
extern void hash_die();
extern char *hash_apply();

extern VOIDSTAR init_stack EXT0();
extern VOIDSTAR pop_stack EXT1(VOIDSTAR);
extern void push_stack EXT2(VOIDSTAR, VOIDSTAR);
extern void flush_stack EXT1(VOIDSTAR);

extern char *range_name EXT1(struct rng *);

extern unsigned char *parse_and_compile EXT1(char *);
extern void byte_free EXT1(unsigned char *);
extern int is_constant EXT1(unsigned char *);

extern unsigned char parse_cell_or_range EXT2(char **,struct rng *);

extern void pr_cell EXT3(CELLREF, CELLREF, CELL *);

extern char *cell_name EXT2(CELLREF, CELLREF);
extern void update_cell EXT1(CELL *);

extern CELLREF cur_row;
extern CELLREF cur_col;
extern unsigned short current_cycle;

void add_range_ref EXT1(struct rng *);
struct var *find_or_make_var EXT2(char *,int);
void push_refs EXT1(struct ref_fm *);
void flush_old_value EXT0();

void push_cell EXT2(CELLREF, CELLREF);

static void add_ref_fm EXT3(struct ref_fm **, CELLREF, CELLREF);
static void flush_ref_fm EXT3(struct ref_fm **, CELLREF, CELLREF);
static void flush_range_ref EXT3(struct rng *, CELLREF, CELLREF);
void add_ref_to EXT1(int);
#ifdef SPLIT_REFS

/* These two are tunable paramaters */

#define REF_START	3
#define REF_INC		*=2

#else
static void flush_ref_to EXT1(struct ref_to **);
static void flush_fm_ref EXT1(struct ref_fm *);
#endif

/* More tunable paramaters */

#define FIFO_START	40
#define FIFO_INC	*=2

#define TO_MAGIC(row,col)	(((long)(row)<<BITS_PER_CELLREF)|(col))
#define MAGIC_ROW(magic)	(((magic)>>BITS_PER_CELLREF)&CELLREF_MASK)
#define MAGIC_COL(magic)	((magic)&CELLREF_MASK)

#define BETWEEN(mid,lo,hi)	((mid>=lo)&&(mid<=hi))

static VOIDSTAR moving;

/* These three for async cell updating */
extern unsigned signal_ticks;

int timer_active = 0;
struct ref_fm *timer_cells;

CELL *my_cell;

#ifdef TEST
extern int debug;
#endif

/* Functions for dealing exclusively with variables */
VOIDSTAR the_vars;

struct value {
	int	type;
	union vals c_z;
};

/* For the fifo-buffer */
struct pos {
	CELLREF row;
	CELLREF col;
};

struct cell_buf {
	unsigned int size;
	struct pos *buf;
	struct pos *push_to_here;
	struct pos *pop_frm_here;
};


/* Set the cell ROW,COL to STRING, parsing string as needed */
void
set_cell FUN3(CELLREF, row, CELLREF, col, char *, string)
{
	unsigned char *ret;

	cur_row=row;
	cur_col=col;

#ifdef TEST
	if(!string) {
		error_msg("Null string to set_cell %s",cell_name(row,col));
		return;
	}
#endif
	while(*string==' ')
		string++;

	if(!*string) {
		my_cell=find_cell(cur_row,cur_col);
		if(!my_cell)
			return;
		flush_old_value();
		return;
	}

	my_cell=find_or_make_cell(cur_row,cur_col);
	flush_old_value();

	ret=parse_and_compile(string);
	my_cell->cell_formula=ret;
}

/* new_value() calls set_cell, but refuses to change locked cells, and
   updates and prints the results.  It returns an error msg on error. . .
 */
char *
new_value FUN3(CELLREF, row, CELLREF, col, char *, string)
{
	CELL *cp;
	extern int default_lock;

	cp=find_cell(row,col);
	if(((!cp || GET_LCK(cp)==LCK_DEF) && default_lock==LCK_LCK) || (cp && GET_LCK(cp)==LCK_LCK)) {
		return "cell is locked";
	}

	set_cell(row, col, string);
	if(my_cell) {
		update_cell(my_cell);
		if(is_constant(my_cell->cell_formula)) {
			byte_free(my_cell->cell_formula);
			my_cell->cell_formula=0;
		}
		pr_cell(row,col,my_cell);
		my_cell=0;
	}
	return 0;
}

/* This sets the cell to a constant, stored in VALUE, whose type is in TYPE */
char *
set_new_value FUN4(CELLREF,row, CELLREF,col, int,type, union vals *,value)
{
	CELL *cp;
	extern int default_lock;

	if(type==TYP_ERR)
		type=0;
	cur_row=row;
	cur_col=col;
	if(type==0) {
		cp=find_cell(row,col);
		if(cp && GET_TYP(cp)) {
			if((GET_LCK(cp)==LCK_DEF && default_lock==LCK_LCK) || GET_LCK(cp)==LCK_LCK)
				return "cell is locked";
			my_cell=cp;
			flush_old_value();
			SET_TYP(cp,0);
		}
		my_cell=0;
		return 0;
	} else {
		cp=find_or_make_cell(row,col);
		if((GET_LCK(cp)==LCK_DEF && default_lock==LCK_LCK) || GET_LCK(cp)==LCK_LCK)
			return "cell is locked";
		my_cell=cp;
		flush_old_value();
		SET_TYP(cp,type);
		/* cp->c_z= *value; */
		switch(type) {
		case TYP_FLT:
			cp->cell_flt=value->c_d;
			cp->cell_formula=0;
			break;

		case TYP_INT:
			cp->cell_int=value->c_l;
			cp->cell_formula=0;
			break;

		case TYP_STR:
			cp->cell_str=strdup(value->c_s);
			cp->cell_formula=0;
			break;

		case TYP_BOL:
			cp->cell_bol=value->c_i;
			cp->cell_formula=0;
			break;

		case TYP_ERR:
			cp->cell_err=value->c_i;
			cp->cell_formula=0;
			break;
#ifdef TEST
		default:
			panic("Unknown type %d in set_new_value",GET_TYP(cp));
#endif
		}
	}
	push_refs(cp->cell_refs_from);
	pr_cell(row,col,cp);
	my_cell=0;
	return 0;
}

/* We're reading in a cell, whose formula is FORM, and whose current value
   is VAL.  Parse both of them. . .  (Parsing of VAL is quite primitive)
 */
char *
read_new_value FUN4(CELLREF, row, CELLREF, col, char *, form, char *,val)
{
	unsigned char *new_bytes;
	extern double __plinf,__neinf,__nan;

	cur_row=row;
	cur_col=col;
	my_cell=find_or_make_cell(cur_row, cur_col);
	flush_old_value();
	SET_TYP(my_cell,0);

	if(form) {
		new_bytes=parse_and_compile(form);
		my_cell->cell_formula=new_bytes;
	}

	if(val) {
		if(val[0]=='"') {
			char *sp,*nsp;

			sp=val+1;
			SET_TYP(my_cell,TYP_STR);
			while(*sp)
				sp++;
			if(*--sp!='"') {
				if(*sp=='\r' && sp[-1]=='"')
					--sp;
				else
					panic("Can't find \" in read_new value");
			}
			*sp='\0';
			nsp=my_cell->cell_str=ck_malloc(sp-val);
			for(sp=val+1;*sp;)
				*nsp++= *sp++;
			*nsp++='\0';
		} else if(isdigit(val[0]) || val[0]=='.' || val[0]=='-' || val[0]=='+') {
			char *v;

			v=val;
			SET_TYP(my_cell,TYP_INT);
			my_cell->cell_int=astol(&v);
			if(*v) {
				SET_TYP(my_cell,TYP_FLT);
				v=val;
				my_cell->cell_flt=astof(&v);
				if(*v)
					return "unknown number";
			}
		} else if(val[0]=='#') {
			char **en;
			extern int stricmp();

			if(!stricmp(tname,val)) {
				SET_TYP(my_cell,TYP_BOL);
				my_cell->cell_bol=1;
			} else if(!stricmp(fname,val)) {
				SET_TYP(my_cell,TYP_BOL);
				my_cell->cell_bol=0;
			} else if(!stricmp(iname,val)) {
				SET_TYP(my_cell,TYP_FLT);
				my_cell->cell_flt=__plinf;
			} else if(!stricmp(iname,val)) {
				SET_TYP(my_cell,TYP_FLT);
				my_cell->cell_flt=__plinf;
			} else if(!stricmp(mname,val)) {
				SET_TYP(my_cell,TYP_FLT);
				my_cell->cell_flt=__neinf;
			} else if(!stricmp(nname,val)) {
				SET_TYP(my_cell,TYP_FLT);
				my_cell->cell_flt=__nan;
			} else {
				SET_TYP(my_cell,TYP_ERR);
				for(en=ename;*en;en++)
					if(!stricmp(*en,val))
						break;
				if(*en)
					my_cell->cell_err=en- &ename[0];
				else
					my_cell->cell_err=1;
			}
		} else
			panic("What is a '%s'?",val);
	}

	my_cell=0;
	return 0;
}

/* This moves the contents, format, etc from RF,CF to RT,CT  RF or RT may be
   NON_ROW, in which case the cell's contents are moved to/from a static
   storage area.  Moving anything from NON_ROW before moving anything into it
   or moving two things at a time into NON_ROW are both bad ideas. . .

   Also note that move_cell does not call move_outside, which may or may not
   be a bug. . .  Move_cell is only called as part of sorting, which is why 
   we may *not* want to call move_outside. . .
 */

void
move_cell FUN4(CELLREF,rf, CELLREF,cf, CELLREF,rt, CELLREF,ct)
{
	CELL *cpf;

	static CELLREF non_rf,non_cf;
	static struct cell non_cell;

	extern void shift_formula();

	if(rf==NON_ROW) {
		cur_row=rt;
		cur_col=ct;
		my_cell=find_cell(cur_row,cur_col);
		if(my_cell)
			flush_old_value();
		else if(!non_cell.cell_flags && !non_cell.cell_formula)
			return;
		else
			my_cell=find_or_make_cell(cur_row,cur_col);

		my_cell->cell_flags=non_cell.cell_flags;
		my_cell->cell_refs_to=non_cell.cell_refs_to;
		my_cell->cell_formula=non_cell.cell_formula;
		my_cell->cell_cycle=non_cell.cell_cycle;
		my_cell->c_z=non_cell.c_z;
		push_refs(my_cell->cell_refs_from);
		if(my_cell->cell_refs_to)
			shift_formula(rt-non_rf,ct-non_cf);
		my_cell=0;
		return;
	}

	cpf=find_cell(rf,cf);

	if(rt==NON_ROW) {
		non_rf=rf;
		non_cf=cf;
		if(!cpf)
			bzero(&non_cell,sizeof(non_cell));
		else {
			non_cell.cell_flags=cpf->cell_flags;
			non_cell.cell_refs_to=cpf->cell_refs_to;
			non_cell.cell_formula=cpf->cell_formula;
			non_cell.cell_cycle=cpf->cell_cycle;
			non_cell.c_z=cpf->c_z;
			cpf->cell_flags=0;
			cpf->cell_refs_to=0;
			cpf->cell_formula=0;
			cpf->cell_cycle=0;
		}
		return;
	}

	cur_row=rt;
	cur_col=ct;
	my_cell=find_cell(cur_row,cur_col);
	if((!cpf || (!cpf->cell_flags && !cpf->cell_formula)) && !my_cell)
		return;
	if(!my_cell) {
		my_cell=find_or_make_cell(cur_row,cur_col);
		cpf=find_cell(rf,cf);	/* FOO */
	} else
		flush_old_value();

	if(!cpf)
		return;

	my_cell->cell_flags=cpf->cell_flags;
	my_cell->cell_refs_to=cpf->cell_refs_to;
	my_cell->cell_formula=cpf->cell_formula;
	my_cell->cell_cycle=cpf->cell_cycle;
	my_cell->c_z=cpf->c_z;

	cpf->cell_flags=0;
	cpf->cell_refs_to=0;
	cpf->cell_formula=0;
	cpf->cell_cycle=0;

	push_refs(my_cell->cell_refs_from);
	if(my_cell->cell_refs_to)
		shift_formula(rt-rf,ct-cf);
	my_cell=0;
}

void
copy_cell FUN4(CELLREF,rf, CELLREF,cf, CELLREF,rt, CELLREF,ct)
{
	CELL *cpf;

	cpf=find_cell(rf,cf);
	cur_row=rt;
	cur_col=ct;
	my_cell=find_cell(cur_row,cur_col);
	if((!cpf || (!cpf->cell_flags && !cpf->cell_formula)) && !my_cell)
		return;
	if(!my_cell) {
		my_cell=find_or_make_cell(cur_row,cur_col);
		cpf=find_cell(rf,cf);	/* FOO */
	} else
		flush_old_value();

	if(!cpf)
		return;

	my_cell->cell_flags=cpf->cell_flags;
	my_cell->cell_cycle=cpf->cell_cycle;

#ifdef SPLIT_REFS
	if(cpf->cell_refs_to {
		size_t siz;


		siz=sizeof(struct ref_to)+(cpf->cell_refs_to->refs_alloc-1)*sizeof(unsigned char);
		my_cell->cell_refs_to=ck_malloc(siz);
		bcopy(cpf->cell_refs_to,my_cell->cell_refs_to,siz);
	} else
		my_cell->cell_refs_to=0;
#else
	my_cell->cell_refs_to=cpf->cell_refs_to;
	if(my_cell->cell_refs_to)
		my_cell->cell_refs_to->refs_refcnt++;
#endif

	if(GET_TYP(my_cell)==TYP_STR)
		my_cell->cell_str=strdup(cpf->cell_str);
	else
		my_cell->c_z=cpf->c_z;

	if(cpf->cell_formula) {
		unsigned char *fp;
		unsigned char *hi;
		unsigned char byte;
		CELLREF trr,tcc;
		struct rng trng;
		struct function *f;
		size_t len;
		struct var *v;
		CELL *tcp;

		fp=cpf->cell_formula;
		hi=0;
		if(!moving)
			moving=init_stack();
		while((byte= *fp++)!=ENDCOMP) {
			if(byte<USR1)
				f= &the_funs[byte];
			else if(byte<SKIP) {
				int tmp;
#ifdef TEST
				extern int n_usr_funs;

				if(byte-USR1>=n_usr_funs)
					panic("Only have %d usr-function slots, but found byte for slot %d",n_usr_funs,1+byte-USR1);
#endif
				tmp= *fp++;
				f= &usr_funs[byte-USR1][tmp];
			} else
				f= &skip_funs[byte-SKIP];

			if(f->fn_argn&X_J)
				fp++;
			else if(f->fn_argn&X_JL)
				fp+=2;

			if((f->fn_argn&X_ARGS)==X_AN)
				fp++;

			switch(byte) {
			case CONST_FLT:
				fp+=sizeof(double);
				break;

			case CONST_INT:
				fp+=sizeof(long);
				break;

			case CONST_STR:
				if(!hi)
					hi=fp+fp[-1];
				break;

			case CONST_STR_L:
				if(!hi)
					hi=fp+fp[-2]+((unsigned)(fp[-1])<<8);
				break;

			case CONST_ERR:
				fp+=1/* +sizeof(char *) */;
				break;

			case VAR:
				bcopy(fp,&v,sizeof(struct var *));
				fp+=sizeof(struct var *);
				add_ref_fm(&(v->var_ref_fm),cur_row,cur_col);
				switch(v->var_flags) {
				case VAR_UNDEF:
					break;
				case VAR_CELL:
					tcp=find_cell(v->v_rng.lr,v->v_rng.lc);
					add_ref_fm(&(tcp->cell_refs_from),cur_row,cur_col);
					break;
				case VAR_RANGE:
					add_range_ref(&(v->v_rng));
					break;
				}
				break;

			case R_CELL:
			case R_CELL|COLREL:
			case R_CELL|ROWREL:
			case R_CELL|ROWREL|COLREL:
				push_stack(moving,fp);
				fp+=EXP_ADD;
				break;

			case RANGE:
			case RANGE|LRREL:
			case RANGE|LRREL|LCREL:
			case RANGE|LRREL|LCREL|HCREL:
			case RANGE|LRREL|HCREL:
			case RANGE|LRREL|HRREL:
			case RANGE|LRREL|HRREL|LCREL:
			case RANGE|LRREL|HRREL|LCREL|HCREL:
			case RANGE|LRREL|HRREL|HCREL:
			case RANGE|HRREL:
			case RANGE|HRREL|LCREL:
			case RANGE|HRREL|LCREL|HCREL:
			case RANGE|HRREL|HCREL:
			case RANGE|LCREL:
			case RANGE|LCREL|HCREL:
			case RANGE|HCREL:
				push_stack(moving,fp);
				fp+=EXP_ADD_RNG;
				break;

			default:
				break;
			}
		}
		if(!hi)
			hi=fp;
		else
			hi+=strlen((char *)hi);
		hi++;
		len=hi-cpf->cell_formula;
		my_cell->cell_formula=cpf->cell_formula;
		cpf->cell_formula=ck_malloc(hi-cpf->cell_formula);
		bcopy(my_cell->cell_formula,cpf->cell_formula,len);
		while(fp=pop_stack(moving)) {
			byte= fp[-1];
			if((byte|ROWREL|COLREL)==(R_CELL|ROWREL|COLREL)) {
				trr=GET_ROW(fp);
				tcc=GET_COL(fp);
/*				if((*fp)&ROWREL) {
					trr+=rt-rf;
					PUT_ROW(fp,trr);
				}  pass travis*/
				if(byte&ROWREL) {
					trr+=rt-rf;
					PUT_ROW(fp,trr);
				}
				if(byte&COLREL) {
					tcc+=ct-cf;
					PUT_COL(fp,tcc);
				}
				tcp=find_or_make_cell(trr,tcc);
				add_ref_fm(&(tcp->cell_refs_from),cur_row,cur_col);
			}
#ifdef TEST
			else if((byte|LRREL|HRREL|LCREL|HCREL)!=
				(RANGE|LRREL|HRREL|LCREL|HCREL))
				panic("Unknown byte %x in copy_cell",byte);
#endif
			else {
				GET_RNG(fp,&trng);
				if(byte&LRREL)
					trng.lr+=rt-rf;
				if(byte&HRREL)
					trng.hr+=rt-rf;
				if(byte&LCREL)
					trng.lc+=ct-cf;
				if(byte&HCREL)
					trng.hc+=ct-cf;
				PUT_RNG(fp,&trng);
				add_range_ref(&trng);
			}
		}
		update_cell(my_cell);
	} else {
		my_cell->cell_formula=0;
	}
	pr_cell(cur_row,cur_col,my_cell);

	push_refs(my_cell->cell_refs_from);
	my_cell=0;
}

/* Take away the value of CP.  This means getting rid of all the references
   to it, etc.
 */
void
flush_old_value FUN0()
{
	struct ref_to *ref;
	unsigned char *refloc;
	int n;
	unsigned char byte;
	CELL *other_cell;
	struct var *varp;

	ref=my_cell->cell_refs_to;
	if(ref) {
		for(n=0;n<ref->refs_used;n++) {
			/* Switch on formula[ref->to_refs[n]] */
			refloc= &(my_cell->cell_formula[ref->to_refs[n]]);
			byte= refloc[0];
			switch (byte) {
			case F_ROW:
			case F_COL:
				break;

			case R_CELL:
			case R_CELL|ROWREL:
			case R_CELL|COLREL:
			case R_CELL|ROWREL|COLREL:
				other_cell=find_cell(GET_ROW(refloc+1), GET_COL(refloc+1));
				if(other_cell)
					flush_ref_fm(&(other_cell->cell_refs_from), cur_row, cur_col);
#ifdef TEST
				else
					error_msg("Can't find other_cell in flush_old_value");
#endif
				break;
			case RANGE:
			case RANGE|LRREL:
			case RANGE|LRREL|LCREL:
			case RANGE|LRREL|LCREL|HCREL:
			case RANGE|LRREL|HCREL:
			case RANGE|LRREL|HRREL:
			case RANGE|LRREL|HRREL|LCREL:
			case RANGE|LRREL|HRREL|LCREL|HCREL:
			case RANGE|LRREL|HRREL|HCREL:
			case RANGE|HRREL:
			case RANGE|HRREL|LCREL:
			case RANGE|HRREL|LCREL|HCREL:
			case RANGE|HRREL|HCREL:
			case RANGE|LCREL:
			case RANGE|LCREL|HCREL:
			case RANGE|HCREL:
				{
					struct rng rng;

					GET_RNG(refloc+1,&rng);
					flush_range_ref(&rng,cur_row,cur_col);
				}
				break;

			case VAR:
				bcopy(&refloc[1],&varp,sizeof(struct var *));
				flush_ref_fm(&(varp->var_ref_fm),cur_row,cur_col);
				if(varp->var_flags==VAR_CELL) {
					other_cell=find_cell(varp->v_rng.lr,varp->v_rng.lc);
					if(other_cell)
						flush_ref_fm(&(other_cell->cell_refs_from),cur_row,cur_col);
				} else if(varp->var_flags==VAR_RANGE)
					flush_range_ref(&(varp->v_rng),cur_row,cur_col);
#ifdef TEST
				else if(varp->var_flags!=VAR_UNDEF)
					panic("Unknown var type %d",varp->var_flags);
#endif
				break;

			default:
			{
				struct function *fun;

				if(byte<USR1)
					fun= &the_funs[byte];
#ifdef TEST
				else if(byte>=SKIP)
					fun=0,panic("SKIP? in flush_old_value()");
#endif
				else
					fun= &usr_funs[byte][refloc[1]];

				if(fun->fn_comptype&C_T) {
#ifdef TEST
					if(!timer_cells || !timer_cells->refs_used)
						panic("No timer cells in flush_timer_cell");
#endif
					flush_ref_fm(&timer_cells,cur_row,cur_col);
					if(!timer_cells || timer_cells->refs_used==0) {
						timer_active=0;
#ifndef __TURBOC__
						ualarm(0,0);
#endif
					}
					break;
				} else
					error_msg("Bad ref_to of %d.%x ignored",ref->to_refs[n],byte);
			}
				break;
			}
		}
#ifdef SPLIT_REFS
		ref->refs_used=0;
#else
		flush_ref_to(&(my_cell->cell_refs_to));
#endif
	}
	if(my_cell->cell_formula) {
		byte_free(my_cell->cell_formula);
		my_cell->cell_formula=0;
	}
	if(GET_TYP(my_cell)==TYP_STR)
		free(my_cell->cell_str);
	SET_TYP(my_cell,0);
}

/* --------- Routines for dealing with cell references to other cells ------ */

void
add_ref FUN2(CELLREF,row, CELLREF,col)
{
	CELL *other_cell;

	other_cell=find_or_make_cell(row,col);
	add_ref_fm(&(other_cell->cell_refs_from), cur_row, cur_col);
}

void
add_range_ref FUN1(struct rng *,rng)
{
	CELL *other_cell;
#ifndef SPLIT_REFS
	struct ref_fm *oldref,*newref;
	struct ref_fm nonref;
#endif
	/* This is horribly inefficient:  Simply referencing a cell makes
	   it appear.  On the other hand, there is no other easy way to deal
	   with the references to the cells (That I know of, anyway) */
	make_cells_in_range(rng);
#ifndef SPLIT_REFS
	/* Be efficient:  If cells in the range currently have the same
	   references, they'll have the same references afterward, so just
	   adjust the refcounts */
	nonref.refs_refcnt=1;
	other_cell=next_cell_in_range();
	oldref=other_cell->cell_refs_from;
	if(oldref && oldref->refs_refcnt==1)
		oldref= &nonref;

	add_ref_fm(&(other_cell->cell_refs_from), cur_row, cur_col);
	newref=other_cell->cell_refs_from;
	while(other_cell=next_cell_in_range()) {
		if(other_cell->cell_refs_from==oldref) {
			if(oldref) {
				if(oldref->refs_refcnt==1) {
					flush_fm_ref(oldref);
					oldref= &nonref;
				} else
					oldref->refs_refcnt--;
			}
			other_cell->cell_refs_from=newref;
			newref->refs_refcnt++;
		} else if(oldref== &nonref && (!other_cell->cell_refs_from || other_cell->cell_refs_from->refs_refcnt>1)) {
			oldref=other_cell->cell_refs_from;
			add_ref_fm(&(other_cell->cell_refs_from), cur_row, cur_col);
			newref=other_cell->cell_refs_from;
		} else
			add_ref_fm(&(other_cell->cell_refs_from), cur_row, cur_col);
	}
	/* if(oldref && oldref->refs_refcnt==0) {
		oldref->refs_refcnt=1;
		flush_fm_ref(oldref);
	} */
#else
	while(other_cell=next_cell_in_range())
		add_ref_fm(&(other_cell->cell_refs_from), cur_row, cur_col);
#endif
}

static void
flush_range_ref FUN3(struct rng *,rng, CELLREF,rr, CELLREF,cc)
{
	CELL *other_cell;
#ifndef SPLIT_REFS
	struct ref_fm *oldref,*newref;
	struct ref_fm nonref;
#endif
	/* This is horribly inefficient:  Simply referencing a cell makes
	   it appear.  On the other hand, there is no other easy way to deal
	   with the references to the cells (That I know of, anyway) */
	find_cells_in_range(rng);
#ifndef SPLIT_REFS
	/* Be efficient:  If cells in the range currently have the same
	   references, they'll have the same references afterward, so just
	   adjust the refcounts */
	nonref.refs_refcnt=1;
	other_cell=next_cell_in_range();
	oldref=other_cell->cell_refs_from;
	if(oldref && oldref->refs_refcnt==1)
		oldref= &nonref;

	flush_ref_fm(&(other_cell->cell_refs_from), rr, cc);
	newref=other_cell->cell_refs_from;
	while(other_cell=next_cell_in_range()) {
		if(other_cell->cell_refs_from==oldref) {
			if(oldref) {
				if(oldref->refs_refcnt==1) {
					flush_fm_ref(oldref);
					oldref= &nonref;
				} else
					oldref->refs_refcnt--;
			}
			other_cell->cell_refs_from=newref;
			if(newref)
				newref->refs_refcnt++;
		} else if(oldref== &nonref && (!other_cell->cell_refs_from || other_cell->cell_refs_from->refs_refcnt>1)) {
			oldref=other_cell->cell_refs_from;
			flush_ref_fm(&(other_cell->cell_refs_from), rr, cc);
			newref=other_cell->cell_refs_from;
		} else
			flush_ref_fm(&(other_cell->cell_refs_from), rr, cc);
	}
#else
	while(other_cell=next_cell_in_range())
		flush_ref_fm(&(other_cell->cell_refs_from), rr, cc);
#endif
}

/* SPLIT_REFS should probably go away, since it is a performance loss.
   When it is defined, we allocate a a reference structure for each cell with
   references, and update them individually.

   The default is to allocate one reference structure for each *different*
   reference, and have multiple cells point to it.  We use reference counts
   to keep track of when to delete the reference structure.  This would
   appear to be a performance loss, but because of the increased efficiency
   in referencing ranges, it may actually be faster.  It also uses *far* less
   memory than SPLIT_REFS */


#ifndef SPLIT_REFS
#ifdef __TURBOC__
#define FM_HASH_NUM 51
#define TO_HASH_NUM 13
#else
#define FM_HASH_NUM 503
#define TO_HASH_NUM 29
#endif
#ifdef TEST
static int fm_misses = 0;
static int to_misses = 0;
#endif

static struct ref_fm *fm_list[FM_HASH_NUM];
static struct ref_fm *fm_tmp_ref;
static unsigned fm_tmp_ref_alloc;

static struct ref_to *to_list[TO_HASH_NUM];
static struct ref_to *to_tmp_ref;
static unsigned to_tmp_ref_alloc;

void
flush_refs FUN0()
{
	int n;
	struct ref_fm *ftmp,*oftmp;
	struct ref_to *ttmp,*ottmp;

	for(n=0;n<FM_HASH_NUM;n++) {
		for(ftmp=fm_list[n];ftmp;ftmp=oftmp) {
			oftmp=ftmp->refs_next;
			free(ftmp);
		}
		fm_list[n]=0;
	}
	for(n=0;n<TO_HASH_NUM;n++) {
		for(ttmp=to_list[n];ttmp;ttmp=ottmp) {
			ottmp=ttmp->refs_next;
			free(ttmp);
		}
		to_list[n]=0;
	}
}

static struct ref_fm *
find_fm_ref FUN0()
{
	struct ref_fm *tmp;
	int n;
	unsigned long hash;

#if 1
	for(hash=0,n=0;n<fm_tmp_ref->refs_used;n++) {
		hash+=(n+1) * (((fm_tmp_ref->fm_refs[n].ref_row)<<BITS_PER_CELLREF) + 
				fm_tmp_ref->fm_refs[n].ref_col);
	}
	hash%=FM_HASH_NUM;
#else
	hash=fm_tmp_ref->refs_used;
#endif
	for(tmp=fm_list[hash];tmp;tmp=tmp->refs_next) {
		if(tmp->refs_used!=fm_tmp_ref->refs_used)
			continue;
		if(!bcmp(tmp->fm_refs,fm_tmp_ref->fm_refs,fm_tmp_ref->refs_used*sizeof(struct ref_array))) {
			tmp->refs_refcnt++;
			return tmp;
		}
#ifdef TEST
		else
			fm_misses++;
#endif
	}

	tmp=ck_malloc(sizeof(struct ref_fm)+(fm_tmp_ref->refs_used-1)*sizeof(struct ref_array));
	tmp->refs_next=fm_list[hash];
	fm_list[hash]=tmp;
	tmp->refs_refcnt=1;
	tmp->refs_used=fm_tmp_ref->refs_used;
	bcopy(fm_tmp_ref->fm_refs,tmp->fm_refs,tmp->refs_used*sizeof(struct ref_array));

	return tmp;
}

static void flush_fm_ref FUN1(struct ref_fm *,old)
{
	struct ref_fm *tmp;
	int n;
	unsigned long hash;

	--(old->refs_refcnt);

#ifdef DEFER_FREE
	return;
#endif
	if(!old->refs_refcnt) {
#if 1
		for(hash=0,n=0;n<old->refs_used;n++) {
			hash+=(n+1) * (((old->fm_refs[n].ref_row)<<BITS_PER_CELLREF) + 
				old->fm_refs[n].ref_col);
		}
		hash%=FM_HASH_NUM;
#else
		hash=old->refs_used;
#endif
		if(fm_list[hash]==old)
			fm_list[hash]=old->refs_next;
		else {
			for(tmp=fm_list[hash];tmp && tmp->refs_next!=old;tmp=tmp->refs_next)
				;
#ifdef TEST
			if(!tmp) {
				error_msg("Old not in refs_list in flush_fm_ref(%p)",old);
				return;
			}
#endif
			tmp->refs_next=old->refs_next;
		}
		free(old);
	}
}

static void
add_ref_fm FUN3(struct ref_fm **, where, CELLREF, r, CELLREF, c)
{
	struct ref_fm *from;
	int n;

	from= *where;
	if(!from) {
		if(!fm_tmp_ref) {
			fm_tmp_ref=ck_malloc(sizeof(struct ref_fm));
			fm_tmp_ref_alloc=1;
		}
		fm_tmp_ref->refs_used=1;
		fm_tmp_ref->fm_refs[0].ref_row=r;
		fm_tmp_ref->fm_refs[0].ref_col=c;
	} else {
		if(fm_tmp_ref_alloc<=from->refs_used) {

			fm_tmp_ref=ck_realloc(fm_tmp_ref,sizeof(struct ref_fm)
					+from->refs_used*sizeof(struct ref_array));
			fm_tmp_ref_alloc=from->refs_used+1;
		}
		fm_tmp_ref->refs_used=from->refs_used+1;
		n=0;
		while(n<from->refs_used
 		   && (from->fm_refs[n].ref_row<r
		       || (from->fm_refs[n].ref_row==r && from->fm_refs[n].ref_col<=c))) {
			fm_tmp_ref->fm_refs[n]=from->fm_refs[n];
			n++;
		}
		fm_tmp_ref->fm_refs[n].ref_row=r;
		fm_tmp_ref->fm_refs[n].ref_col=c;
		while(n<from->refs_used) {
			fm_tmp_ref->fm_refs[n+1]=from->fm_refs[n];
			n++;
		}
	}
	*where=find_fm_ref();
	if(from)
		flush_fm_ref(from);
}

static void
flush_ref_fm FUN3(struct ref_fm **, where, CELLREF, r, CELLREF, c)
{
	struct ref_fm *from;
	int n;

	from= *where;
#ifdef TEST
	if(!from) {
		error_msg("No refs in flush_ref_fm(%p,%u,%u)",where,r,c);
		return;
	}
#endif
	if(from->refs_used==1) {
		*where=0;
		flush_fm_ref(from);
		return;
	}
	fm_tmp_ref->refs_used=from->refs_used-1;
	n=0;
	while(n<from->refs_used
 	   && (from->fm_refs[n].ref_row<r
	       || (from->fm_refs[n].ref_row==r && from->fm_refs[n].ref_col<c))) {
		fm_tmp_ref->fm_refs[n]=from->fm_refs[n];
		n++;
	}
#ifdef TEST
	if(n==from->refs_used) {
		error_msg("No refs from %u,%u in %p in flush_refs_fm",r,c,where);
		return;
	}
#endif
	while(n<fm_tmp_ref->refs_used) {
		fm_tmp_ref->fm_refs[n]=from->fm_refs[n+1];
		n++;
	}
	*where=find_fm_ref();
	flush_fm_ref(from);
}

#ifdef TEST
extern char print_buf[];

void
dbg_print_ref_fm FUN1(struct ref_fm *,rf)
{
	int nr;
	char *bufp;

	if(rf) {
		text_line("fm %p: refcnt %u  next %p  used %u",
			rf,rf->refs_refcnt,rf->refs_next,rf->refs_used);
		for(nr=0,bufp=print_buf;nr<rf->refs_used;nr++) {
			(void)sprintf(bufp," %s",cell_name(rf->fm_refs[nr].ref_row,rf->fm_refs[nr].ref_col));
			if(nr%10==9) {
				text_line(print_buf);
				bufp=print_buf;
			} else
				bufp+=strlen(bufp);
		}
		if(nr%10)
			text_line(print_buf);
	}
}
#endif

static struct ref_to *
find_to_ref FUN0()
{
	struct ref_to *tmp;
	int n;
	unsigned long hash;

	/* error_msg("find_to_ref %u %u",to_tmp_ref->refs_used,to_tmp_ref->to_refs[0]); */
#if 1
	for(hash=0,n=0;n<to_tmp_ref->refs_used;n++)
		hash+= (n+1) * to_tmp_ref->to_refs[n];

	hash%=TO_HASH_NUM;
#else
	hash=to_tmp_ref->refs_used;
#endif
	for(tmp=to_list[hash];tmp;tmp=tmp->refs_next) {
		/* error_msg("%p(%u)->%p  %u %u",tmp,tmp->refs_refcnt,
			tmp->refs_next,tmp->refs_used,tmp->to_refs[0]); */
		if(tmp->refs_used!=to_tmp_ref->refs_used)
			continue;
		if(!bcmp(tmp->to_refs,to_tmp_ref->to_refs,to_tmp_ref->refs_used)) {
			/* error_msg("Hit!"); */
			tmp->refs_refcnt++;
			return tmp;
		}
#ifdef TEST
		else
			to_misses++;
#endif
	}

	/* error_msg("Miss. .."); */
	tmp=ck_malloc(sizeof(struct ref_to)+to_tmp_ref->refs_used-1);
	tmp->refs_next=to_list[hash];
	to_list[hash]=tmp;
	tmp->refs_refcnt=1;
	tmp->refs_used=to_tmp_ref->refs_used;
	bcopy(to_tmp_ref->to_refs,tmp->to_refs,tmp->refs_used);

	return tmp;
}

void
add_ref_to FUN1(int, whereto)
{
	struct ref_to *from;
	int n;

	from= my_cell->cell_refs_to;
	if(!from) {
		if(!to_tmp_ref) {
			to_tmp_ref=ck_malloc(sizeof(struct ref_to));
			to_tmp_ref_alloc=1;
		}
		to_tmp_ref->refs_used=1;
		to_tmp_ref->to_refs[0]=whereto;
	} else {
		if(to_tmp_ref_alloc<=from->refs_used) {

			to_tmp_ref=ck_realloc(to_tmp_ref,sizeof(struct ref_to) +from->refs_used);
			to_tmp_ref_alloc=from->refs_used+1;
		}
		to_tmp_ref->refs_used=from->refs_used+1;
		n=0;
		while(n<from->refs_used && from->to_refs[n]<whereto) {
			to_tmp_ref->to_refs[n]=from->to_refs[n];
			n++;
		}
		to_tmp_ref->to_refs[n]=whereto;
		while(n<from->refs_used) {
			to_tmp_ref->to_refs[n+1]=from->to_refs[n];
			n++;
		}
		flush_ref_to(&(my_cell->cell_refs_to));
	}
	my_cell->cell_refs_to=find_to_ref();
}

static void
flush_ref_to FUN1(struct ref_to **,where)
{
	struct ref_to *tmp;
	struct ref_to *old;
	int n;
	unsigned long hash;

#ifdef TEST
	if(!where || !*where) {
		error_msg("null flush_ref_to(%p)",where);
		return;
	}
#endif
	old= *where;
	*where=0;
	--(old->refs_refcnt);

#ifdef DEFER_FREE
	return;
#endif
	if(!old->refs_refcnt) {
#if 1
		for(hash=0,n=0;n<old->refs_used;n++)
			hash+=(n+1) * old->to_refs[n];

		hash%=TO_HASH_NUM;
#else
		hash=old->refs_used;
#endif
		if(to_list[hash]==old)
			to_list[hash]=old->refs_next;
		else {
			for(tmp=to_list[hash];tmp && tmp->refs_next!=old;tmp=tmp->refs_next)
				;
#ifdef TEST
			if(!tmp) {
				error_msg("Old not in refs_list in flush_to_ref(%p)",old);
				return;
			}
#endif
			tmp->refs_next=old->refs_next;
		}
		free(old);
	}
}

#ifdef TEST
void
dbg_print_ref_to FUN2(struct ref_to *,rt, unsigned char *,form)
{
	int nr;
	char *bufp;

	if(rt) {
		text_line("to %p: refcnt %u  next %p  used %u",
			rt,rt->refs_refcnt,rt->refs_next,rt->refs_used);
		for(nr=0,bufp=print_buf;nr<rt->refs_used;nr++) {
			(void)sprintf(bufp," %3d (%#4x)",rt->to_refs[nr],form[rt->to_refs[nr]]);
			if(nr%7==6) {
				text_line(print_buf);
				bufp=print_buf;
			} else
				bufp+=strlen(bufp);
		}
		if(nr%7)
			text_line(print_buf);
	}
}

void
ref_stats FUN0()
{
	int n;
	int cur;
	struct ref_fm *rf;
	struct ref_to *rt;

	int rf_max = 0;
	int rf_num = 0;
	int rf_shared = 0;
	int rf_saved = 0;
	int rf_zero = 0;

	int rt_max = 0;
	int rt_num = 0;
	int rt_shared = 0;
	int rt_saved = 0;
	int rt_zero = 0;

	for(n=0;n<FM_HASH_NUM;n++) {
		cur=0;
		for(rf=fm_list[n];rf;rf=rf->refs_next) {
			if(rf->refs_refcnt==0)
				rf_zero++;
			if(rf->refs_refcnt>1) {
				rf_shared++;
				rf_saved+=rf->refs_refcnt-1;
			}
			rf_num++;
			cur++;
		}
		if(cur>rf_max)
			rf_max=cur;
	}
	for(n=0;n<TO_HASH_NUM;n++) {
		cur=0;
		for(rt=to_list[n];rt;rt=rt->refs_next) {
			if(rt->refs_refcnt==0)
				rt_zero++;
			if(rt->refs_refcnt>1) {
				rt_shared++;
				rt_saved+=rt->refs_refcnt-1;
			}
			rt_num++;
			cur++;
		}
		if(cur>rt_max)
			rt_max=cur;
	}
	text_line("from: %d refs, max_length %d, shared %d, saved %d, zero_ref %d, missed %d\n",rf_num,rf_max,rf_shared,rf_saved,rf_zero,fm_misses);
	text_line("to: %d refs, max_length %d, shared %d, saved %d, zero_ref %d, missed %d\n",rt_num,rt_max,rt_shared,rt_saved,rt_zero,to_misses);
}
#endif
#else

static void
add_ref_fm FUN3(struct ref_fm **, where, CELLREF, r, CELLREF, c)
{
	struct ref_fm *ref;

	ref= *where;
	if(!ref) {
		*where=ref=ck_malloc(sizeof(struct ref_fm)+(REF_START-1)*sizeof(struct ref_array));
		ref->refs_alloc=REF_START;
		ref->refs_used=0;
	} else if(ref->refs_alloc==ref->refs_used) {
		ref->refs_alloc REF_INC;

		*where=ref=ck_realloc(ref, sizeof(struct ref_fm)+(ref->refs_alloc-1)*sizeof(struct ref_array));
	}
	ref->fm_refs[ref->refs_used].ref_row=r;
	ref->fm_refs[ref->refs_used].ref_col=c;
	ref->refs_used++;
}

static void
flush_ref_fm FUN3(struct ref_fm **, where, CELLREF, r, CELLREF, c)
{
	int n;
	struct ref_fm *ref;

	ref= *where;
#ifdef TEST
	if(!ref) {
		error_msg("%s->No refs in flush_ref_fm(%d,%d)",cell_name(cur_row,cur_col),r,c);
		return;
	}
#endif
	for(n=0;n<ref->refs_used;n++)
		if(ref->fm_refs[n].ref_row==r && ref->fm_refs[n].ref_col==c) {
			ref->fm_refs[n]=ref->fm_refs[ref->refs_used-1];
			--(ref->refs_used);
			return;
		}
#ifdef TEST
	error_msg("%s->Can't flush_ref_fm(%d,%d)",cell_name(cur_row,cur_col),r,c);
	return;
#endif
}

void
add_ref_to FUN1(int, whereto)
{
	struct ref_to *ref;

	ref= my_cell->cell_refs_to;
	if(!ref) {
		my_cell->cell_refs_to=ref=ck_malloc(sizeof(struct ref_to)+(REF_START-1)*sizeof(unsigned char));
		ref->refs_alloc=REF_START;
		ref->refs_used=0;
	} else if(ref->refs_alloc==ref->refs_used) {
		ref->refs_alloc REF_INC;

		my_cell->cell_refs_to=ref=ck_realloc(ref, sizeof(struct ref_to)+
		(ref->refs_alloc-1)*sizeof(unsigned char));
	}
	ref->to_refs[ref->refs_used]=whereto;
	ref->refs_used++;
}

#ifdef TEST

void
dbg_print_ref_fm FUN1(struct ref_fm *,rf)
{
	int nr;
	char *bufp;

	if(rf) {
		text_line("fm %p: alloc %u  used %u",
			rf,rf->refs_alloc,rf->refs_used);
		for(nr=0,bufp=print_buf;nr<rf->refs_used;nr++) {
			(void)sprintf(bufp," %s",cell_name(rf->fm_refs[nr].ref_row,rf->fm_refs[nr].ref_col));
			if(nr%10==9) {
				text_line(print_buf);
				bufp=print_buf;
			} else
				bufp+=strlen(bufp);
		}
		if(nr%10)
			text_line(print_buf);
	}
}

void
dbg_print_ref_to FUN2(struct ref_to *,rt, unsigned char *,form)
{
	int nr;
	char *bufp;

	if(rt) {
		text_line("to %p:  alloc %u  used %u",
			rt,rt->refs_alloc,rt->refs_used);
		for(nr=0,bufp=print_buf;nr<rt->refs_used;nr++) {
			(void)sprintf(bufp," %3d (%#4x)",rt->to_refs[nr],form[rt->to_refs[nr]]);
			if(nr%7==6) {
				text_line(print_buf);
				bufp=print_buf;
			} else
				bufp+=strlen(bufp);
		}
		if(nr%7)
			text_line(print_buf);
	}
}

void
ref_stats FUN0()
{
	CELL *cp;
	extern struct rng all_rng;

	int rf_num = 0;
	int rf_zero = 0;
	int rf_biggest = 0;

	int rt_num = 0;
	int rt_zero = 0;
	int rt_biggest = 0;

	find_cells_in_range(&all_rng);
	while(cp=next_cell_in_range()) {
		if(cp->cell_refs_from) {
			if(cp->cell_refs_from->refs_used>rf_biggest)
				rf_biggest=cp->cell_refs_from->refs_used;
			if(cp->cell_refs_from->refs_used==0)
				rf_zero++;
			rf_num++;
		}
		if(cp->cell_refs_to) {
			if(cp->cell_refs_to->refs_used>rt_biggest)
				rt_biggest=cp->cell_refs_to->refs_used;
			if(cp->cell_refs_to->refs_used==0)
				rt_zero++;
			rt_num++;
		}
	}
	text_line("from: %d refs, biggest %d, zero_ref %d\n",rf_num,rf_biggest,rf_zero);
	text_line("to: %d refs, biggest %d,zero_ref %d\n",rt_num,rt_biggest,rt_zero);
}
#endif

#endif

/* ------------- Routines for dealing with moving cells -------------------- */

static struct rng *shift_fm;
static int shift_ov;
static int shift_dn;

static void shift_var FUN2(char *,name, struct var *,v)
{
	int n;
	int nn;


	n=   (BETWEEN(v->v_rng.hc,shift_fm->lc,shift_fm->hc)<<3)
	   + (BETWEEN(v->v_rng.lc,shift_fm->lc,shift_fm->hc)<<2)
	   + (BETWEEN(v->v_rng.hr,shift_fm->lr,shift_fm->hr)<<1)
	   + BETWEEN(v->v_rng.lr,shift_fm->lr,shift_fm->hr);
	switch(n) {
	case 0:
	case 1:
	case 2:
	case 3:
	case 4:
	case 8:
	case 12:
		/* Null intersection, ignore it */
		break;

	case 5: /* The bottom and right */
	case 6: /* The bottom and left */
	case 9: /* The top and right */
	case 10:/* The top and left */
		/* The var sticks out of the range we're moving */
		/* on two sides.  what should we do? */
		error_msg("'%s' can't be adjusted",v->var_name);
		break;

	case 7: /* v->hc sticks out the right */
	case 11:/* v->lc sticks out the left */
	case 13:/* v->hr sticks out the bottom */
	case 14:/* v->lr sticks out the top */
		/* It only sticks out on one side.  We can
		   (try to) adjust it */
		error_msg("'%s' sticks out; adjusted",v->var_name);
	case 15:	/* var is completely inside the range */
		if(v->var_ref_fm) {
			for(nn=0;nn<v->var_ref_fm->refs_used;nn++) {
				flush_range_ref(&(v->v_rng),
				    v->var_ref_fm->fm_refs[nn].ref_row,
				    v->var_ref_fm->fm_refs[nn].ref_col);
			}
		}
		if(n!=7)
			v->v_rng.hc+=shift_ov;
		if(n!=11)
			v->v_rng.lc+=shift_ov;
		if(n!=13)
			v->v_rng.hr+=shift_dn;
		if(n!=14)
			v->v_rng.lr+=shift_dn;
		if(!v->var_ref_fm)
			return;

		for(n=0;n<v->var_ref_fm->refs_used;n++) {
			cur_row=v->var_ref_fm->fm_refs[n].ref_row;
			cur_col=v->var_ref_fm->fm_refs[n].ref_col;
			add_range_ref(&(v->v_rng));
		}
		break;
#ifdef TEST
	default:
		panic("Bad ###%d",n);
#endif
	}
}

/* All the cells in FM are about to move down DN and over OV, adjust
   everything outside them, etc so that it-all will still work.  This is
   a horrible, but required kludge.
 */
void
  shift_outside FUN3(struct rng *,fm, int,dn, int,ov)
{
  CELL *cp;
  CELL *fcp;
  CELL *tcp;
  int n;
  int fn;
  CELLREF rr,cc;
  CELLREF frr,fcc;
  CELLREF trr,tcc;
  unsigned char *ffp;
  unsigned char *fp;
  struct var *v;
  struct rng orng;
  
  char *ptr;
  unsigned long val;
  static char DEF_REF[] = "DEFREF";
  static char DEF_RNG[] = "DEFRNG";
  
  /* This moves all the variables in FM down DN and over OV
     This also adjusts *all* references to these variables */
  
  shift_fm=fm;
  shift_dn=dn;
  shift_ov=ov;
  for_all_vars(shift_var);
  
  /* We use this stack 'cuz we might need to move a reference to
     somewhere else in the range we're scanning.  If we do, without the
     defer stack, we may accidentally move the reference again. */
  if(!moving)
    moving=init_stack();
  
  find_cells_in_range(fm);
  while(cp=next_row_col_in_range(&rr,&cc)) {
    
    /* Adjust references from inside the region */
    if(cp->cell_refs_to) {
      for(n=0;n<cp->cell_refs_to->refs_used;n++) {
	fp= &(cp->cell_formula[cp->cell_refs_to->to_refs[n]]);
	switch(*fp) {
	case R_CELL:
	case R_CELL|ROWREL:
	case R_CELL|COLREL:
	case R_CELL|ROWREL|COLREL:
	  trr=GET_ROW(fp+1);
	  tcc=GET_COL(fp+1);
	  tcp=find_cell(trr,tcc);
#ifdef TEST
	  if(!tcp)
	    panic("Can't find_cell(%s) for %d ref from %s in shift_stuff()",cell_name(trr,tcc),*fp,cell_name(rr,cc));
#endif
	  fn=BETWEEN(trr,fm->lr,fm->hr) && BETWEEN(tcc,fm->lc,fm->hc);
/*	  frr= MIN(MAX_R,MAX(MIN_R,(fn || (((*fp)&ROWREL))) ? trr+dn : trr));
	  fcc= MIN(MAX_C,MAX(MIN_C,(fn || (((*fp)&COLREL))) ? tcc+ov : tcc));*/
  /* MIN,MAX crap insures frr and fcc are within bounds, this should
     produce an error if out of bounds, but how????, pass travis */
	  frr= (fn || (((*fp)&ROWREL))) ? trr+dn : trr;
	  if (frr>MAX_R || frr<MIN_R ) {frr=MAX_R; fcc=MAX_C;}
	  fcc= (fn || (((*fp)&COLREL))) ? tcc+ov : tcc;
	  if (fcc>MAX_C || fcc<MIN_C )  {frr=MAX_R; fcc=MAX_C;}
/* this way if out of bounds, point cell to the last cell, and hope for an error */
	  if(frr!=trr || fcc!=tcc) {
	    flush_ref_fm(&(tcp->cell_refs_from),rr,cc);
	    PUT_ROW(fp+1,frr);
	    PUT_COL(fp+1,fcc);
	    if(BETWEEN(frr,fm->lr,fm->hr) && BETWEEN(fcc,fm->lc,fm->hc)) {
	      push_stack(moving,(VOIDSTAR)TO_MAGIC(rr,cc));
	      push_stack(moving,(VOIDSTAR)TO_MAGIC(frr,fcc));
	      push_stack(moving,DEF_REF);
	    } else {
	      tcp=find_or_make_cell(frr,fcc);
	      add_ref_fm(&(tcp->cell_refs_from),rr+dn,cc+ov);
	      cp=find_cell(rr,cc);
	    }
	  }
	  break;
	  
	case RANGE:
	case RANGE|LRREL:
	case RANGE|HRREL:
	case RANGE|LCREL:
	case RANGE|HCREL:
	case RANGE|LRREL|HRREL:
	case RANGE|LRREL|LCREL:
	case RANGE|LRREL|HCREL:
	case RANGE|HRREL|LCREL:
	case RANGE|HRREL|HCREL:
	case RANGE|LCREL|HCREL:
	case RANGE|LRREL|LCREL|HCREL:
	case RANGE|LRREL|HRREL|LCREL:
	case RANGE|LRREL|HRREL|HCREL:
	case RANGE|HRREL|LCREL|HCREL:
	case RANGE|LRREL|HRREL|LCREL|HCREL:
	  GET_RNG(fp+1,&orng);
	  flush_range_ref(&orng,rr,cc);
	  
	  fn=   (BETWEEN(orng.hc,fm->lc,fm->hc)<<3)
	    | (BETWEEN(orng.lc,fm->lc,fm->hc)<<2)
	      | (BETWEEN(orng.hr,fm->lr,fm->hr)<<1)
		|  BETWEEN(orng.lr,fm->lr,fm->hr);
	  
	  switch(fn) {
	  case 3:
	  case 5:
	  case 6:
	  case 9:
	  case 10:
	  case 12:
	    error_msg("Range %s at cell %s can't be adjusted",range_name(&orng),cell_name(rr,cc));
	    /* Fallthrough */
	  case 0:
	  case 1:
	  case 2:
	  case 4:
	  case 8:
	    if((*fp)&LRREL)
	      orng.lr+=dn;
	    if((*fp)&HRREL)
	      orng.hr+=dn;
	    if((*fp)&LCREL)
	      orng.lc+=ov;
	    if((*fp)&HCREL)
	      orng.hc+=ov;
	    break;
	    
	  case 7:
	  case 11:
	  case 13:
	  case 14:
	    error_msg("Range %s sticks out at cell %s; adjusted",range_name(&orng),cell_name(rr,cc));
	    /* Fallthrough */
	  case 15:
	    if(fn!=7)
	      orng.hc+=ov;
	    if(fn!=11)
	      orng.lc+=ov;
	    if(fn!=13)
	      orng.hr+=dn;
	    if(fn!=14)
	      orng.lr+=dn;
	    break;
#ifdef TEST
	  default:
	    panic("fn==%d?",fn);
#endif
	  }
	  PUT_RNG(fp+1,&orng);
	  cur_row=rr;
	  cur_col=cc;
	  add_range_ref(&orng);
	  break;
	default:
	  {
	    struct function *fun;
	    
	    if(*fp<USR1)
	      fun= &the_funs[*fp];
#ifdef TEST
	    else if(*fp>=SKIP)
	      fun=0,panic("SKIP?? in shift_outside()");
#endif
	    else
	      fun= &usr_funs[*fp][fp[1]];
	    
	    if(fun->fn_comptype&C_T) {
	      flush_ref_fm(&timer_cells,rr,cc);
	      add_ref_fm(&timer_cells,rr+dn,cc+ov);
	    }
	  }
	  break;
	}
	
      }
    }
    
    /* If nothing referenced them, don't worry about them */
    if(!cp->cell_refs_from)
      continue;
    for(n=0;n<cp->cell_refs_from->refs_used;n++) {
      
      /* For each cell that referenced this one, look
	 at the type of reference involved */
      frr=cp->cell_refs_from->fm_refs[n].ref_row;
      fcc=cp->cell_refs_from->fm_refs[n].ref_col;
      /* Unless it's from inside the region we're moving */
      if(BETWEEN(frr,fm->lr,fm->hr) && BETWEEN(fcc,fm->lc,fm->hc))
	continue;
      
      fcp=find_cell(frr,fcc);
#ifdef TEST
      if(!fcp || !fcp->cell_formula || !fcp->cell_refs_to)
	panic("How'd we get this cell");
#endif
      for(fn=0;fn<fcp->cell_refs_to->refs_used;fn++) {
	ffp= &(fcp->cell_formula[fcp->cell_refs_to->to_refs[fn]]);
	switch(*ffp) {
	case R_CELL:
	case R_CELL|ROWREL:
	case R_CELL|COLREL:
	case R_CELL|ROWREL|COLREL:
	  trr=GET_ROW(ffp+1);
	  tcc=GET_COL(ffp+1);
	  if(trr!=rr || tcc!=cc)
	    /* Not the one we're looking for */
	    break;
	  
	  /* Found it!  Find the cell that fcp
	     should be referencing now */
	  if(!((*ffp)&ROWREL)) {
	    trr+=dn;
	    PUT_ROW(ffp+1, trr);
	  }
	  if(!((*ffp)&COLREL)) {
	    tcc+=ov;
	    PUT_COL(ffp+1, tcc);
	  } else
	    goto nxtref;
	  
	  if(BETWEEN(trr,fm->lr,fm->hr) && BETWEEN(tcc,fm->lc,fm->hc)) {
	    push_stack(moving,(VOIDSTAR )TO_MAGIC(frr,fcc));
	    push_stack(moving,(VOIDSTAR )TO_MAGIC(trr,tcc));
	    push_stack(moving,DEF_REF);
	  } else {
	    cp=find_or_make_cell(trr,tcc);
	    add_ref_fm(&(cp->cell_refs_from),frr,fcc);
	  }
	  goto flushref;
	  
	case VAR:
	  bcopy(&ffp[1],&v,sizeof(struct var *));
	  switch(v->var_flags) {
	  case VAR_UNDEF:
	    break;
	    
	  case VAR_CELL:
	    if(v->v_rng.lr==rr || v->v_rng.lc==cc)
	      /* Right variable */
	      goto nxtref;
	    break;
	    
	  case VAR_RANGE:
	    if(BETWEEN(rr,v->v_rng.lr,v->v_rng.hr) && BETWEEN(cc,v->v_rng.lc,v->v_rng.hc))
	      goto nxtref;
	    break;
#ifdef TEST
	  default:
	    panic("Unknown variable flags %d (%s)",v->var_flags,v->var_name);
#endif
	  }
	  break;
	  
	case RANGE:
	case RANGE|LRREL:
	case RANGE|LRREL|LCREL:
	case RANGE|LRREL|LCREL|HCREL:
	case RANGE|LRREL|HCREL:
	case RANGE|LRREL|HRREL:
	case RANGE|LRREL|HRREL|LCREL:
	case RANGE|LRREL|HRREL|LCREL|HCREL:
	case RANGE|LRREL|HRREL|HCREL:
	case RANGE|HRREL:
	case RANGE|HRREL|LCREL:
	case RANGE|HRREL|LCREL|HCREL:
	case RANGE|HRREL|HCREL:
	case RANGE|LCREL:
	case RANGE|LCREL|HCREL:
	case RANGE|HCREL:
	  GET_RNG(ffp+1,&orng);
	  
	  if(!BETWEEN(rr,orng.lr,orng.hr) || !BETWEEN(cc,orng.lc,orng.hc))
	    break;
	  
	  val=   (BETWEEN(orng.hc,fm->lc,fm->hc)<<3)
	    + (BETWEEN(orng.lc,fm->lc,fm->hc)<<2)
	      + (BETWEEN(orng.hr,fm->lr,fm->hr)<<1)
		+  BETWEEN(orng.lr,fm->lr,fm->hr);
	  
	  if(!(	   *ffp==RANGE
	       || (!dn && ((*ffp)|LRREL|HRREL)==(RANGE|LRREL|HRREL))
	       || (!ov && ((*ffp)|LCREL|HCREL)==(RANGE|LCREL|HCREL))))
	    goto nxtref;
	  
	  if(val!=7 && val!=11 && val!=13 && val!=14 && val!=15) {
#ifdef TEST
	    error_msg("Val %d?",val);
#endif
	    goto nxtref;
	  } else if(val!=15)
	    error_msg("adjusting range %s at %s",range_name(&orng),cell_name(frr,fcc));
	  
	  /* Move the entire damn range */
	  flush_range_ref(&orng,frr,fcc);
	  
	  if(val!=14)
	    orng.lr+=dn;
	  if(val!=13)
	    orng.hr+=dn;
	  if(val!=11)
	    orng.lc+=ov;
	  if(val!=7)
	    orng.hc+=ov;
	  PUT_RNG(ffp+1,&orng);
	  push_stack(moving,(VOIDSTAR)ffp);
	  push_stack(moving,(VOIDSTAR )TO_MAGIC(frr,fcc));
	  push_stack(moving,DEF_RNG);
	  --n;
	  goto nxtref;
#ifdef TEST
	default:
	  {
	    struct function *fun;
	    
	    if(*ffp<USR1)
	      fun= &the_funs[*ffp];
	    else if(*ffp>=SKIP)
	      fun=0,panic("SKIP? in shift_outside()");
	    else
	      fun= &usr_funs[*ffp][ffp[1]];
	    if((fun->fn_comptype&C_T)==0)
	      error_msg("Unknown byte (%d) for reference_to #%d %d",*ffp,fn,fcp->cell_refs_to->to_refs[fn]);
	  }
	  break;
#endif
	}
      }
#ifdef TEST
      error_msg("No refs_to at %s to explain ref_fm at %s",cell_name(frr,fcc),cell_name(rr,cc));
      break;
#endif
    flushref:
      /* If we get here, the ref from frr,fcc should be
	 deleted. */
      flush_ref_fm(&(cp->cell_refs_from),frr,fcc);
      --n;
      
      
    nxtref:
      /* If we get here, we found the reference we were
	 looking for and we want to go on to the next one */
      ;
    }
  }
  while(ptr=pop_stack(moving)) {
    if(ptr==DEF_REF) {
      val=(unsigned long)pop_stack(moving);
      trr=MAGIC_ROW(val);
      tcc=MAGIC_COL(val);
      val=(unsigned long)pop_stack(moving);
      cp=find_or_make_cell(trr,tcc);
      add_ref_fm(&(cp->cell_refs_from),MAGIC_ROW(val),MAGIC_COL(val));
    } else if(ptr==DEF_RNG) {
      val=(unsigned long)pop_stack(moving);
      cur_row=MAGIC_ROW(val);
      cur_col=MAGIC_COL(val);
      ffp=(unsigned char *)pop_stack(moving);
      
      GET_RNG(ffp+1,&orng);
      add_range_ref(&orng);
    }
#ifdef TEST
    else
      panic("Now what (%p)?",ptr);
#endif
  }
  /* flush_stack(moving); */
}

/* The formula in cell my_cell has moved by DN down and OV over, adjust
   everything so it'll still work */
void
shift_formula FUN2(int,dn, int,ov)
{
	int n;
	unsigned char *fp;

	for(n=0;n<my_cell->cell_refs_to->refs_used;n++) {
		fp= &(my_cell->cell_formula[my_cell->cell_refs_to->to_refs[n]]);
		switch(*fp) {
		case F_ROW:
		case F_COL:
			push_cell(cur_row,cur_col);
			break;

		case R_CELL:
		case R_CELL|ROWREL:
		case R_CELL|COLREL:
		case R_CELL|ROWREL|COLREL:
		{
			CELLREF trr,tcc;
			CELL *tcp;

			/* These are more difficult */
			trr=GET_ROW(fp+1);
			tcc=GET_COL(fp+1);
			tcp=find_cell(trr,tcc);
#ifdef TEST
			if(!tcp)
				panic("Can't find_cell(%s) in shift_formula",cell_name(trr,tcc));
#endif
			flush_ref_fm(&(tcp->cell_refs_from),cur_row-dn,cur_col-ov);

			if(((*fp)&ROWREL) && dn) {
				trr+=dn;
				PUT_ROW(fp+1,trr);
			}
			if(((*fp)&COLREL) && ov) {
				tcc+=ov;
				PUT_COL(fp+1,tcc);
			}
			tcp=find_or_make_cell(trr,tcc);
			add_ref_fm(&(tcp->cell_refs_from),cur_row,cur_col);
		}
			break;

		case RANGE:
		case RANGE|LRREL:
		case RANGE|LRREL|LCREL:
		case RANGE|LRREL|LCREL|HCREL:
		case RANGE|LRREL|HCREL:
		case RANGE|LRREL|HRREL:
		case RANGE|LRREL|HRREL|LCREL:
		case RANGE|LRREL|HRREL|LCREL|HCREL:
		case RANGE|LRREL|HRREL|HCREL:
		case RANGE|HRREL:
		case RANGE|HRREL|LCREL:
		case RANGE|HRREL|LCREL|HCREL:
		case RANGE|HRREL|HCREL:
		case RANGE|LCREL:
		case RANGE|LCREL|HCREL:
		case RANGE|HCREL:
		{
			struct rng orng;
			GET_RNG(fp+1,&orng);

			flush_range_ref(&orng,cur_row-dn,cur_col-ov);

			if((*fp)&LRREL)
				orng.lr+=dn;
			if((*fp)&HRREL)
				orng.hr+=dn;
			if((*fp)&LCREL)
				orng.lc+=ov;
			if((*fp)&HCREL)
				orng.hc+=ov;
			PUT_RNG(fp+1,&orng);
			add_range_ref(&orng);
		}
			break;

		case VAR:
		{
			struct var *v;
			struct cell *tcp;

			bcopy(&fp[1],&v,sizeof(struct var *));
			flush_ref_fm(&(v->var_ref_fm),cur_row-dn,cur_col-ov);
			add_ref_fm(&(v->var_ref_fm),cur_row,cur_col);
			switch(v->var_flags) {
			case VAR_UNDEF:
				break;

			case VAR_CELL:
				tcp=find_cell(v->v_rng.lr,v->v_rng.lc);
#ifdef TEST
				if(!tcp)
					panic("Can't find_cell(%s) in shift_formula",cell_name(v->v_rng.lr,v->v_rng.lc));
#endif
				flush_ref_fm(&(tcp->cell_refs_from),cur_row-dn,cur_col-ov);
				add_ref_fm(&(tcp->cell_refs_from),cur_row,cur_col);
				break;

			case VAR_RANGE:
				flush_range_ref(&(v->v_rng),cur_row-dn,cur_col-ov);
				add_range_ref(&(v->v_rng));
				break;

#ifdef TEST
			default:
				panic("Unknown var type %d",v->var_flags);
#endif
			}
		}
			break;

		default:
		{
			struct function *fun;

			if(*fp<USR1)
				fun= &the_funs[*fp];
#ifdef TEST
			else if(*fp>=SKIP)
				fun=0,panic("SKIP? in shift_formula?");
#endif
			else
				fun= &usr_funs[*fp][fp[1]];
				/* These are easy */
			if(fun->fn_comptype&C_T) {
				flush_ref_fm(&timer_cells,cur_row-dn,cur_col-ov);
				add_ref_fm(&timer_cells,cur_row,cur_col);
			}
#ifdef TEST
			else
				panic("How do I deal with byte %d in shift_formula()?",*fp);
#endif
		}
			break;
			
		}
	}
}


/* ---------------- Routines for dealing with async functions -------------- */

#if defined(NO_UALARM)

#ifdef SETITIMER

#include <time.h>

unsigned ualarm FUN2(unsigned,first, unsigned,others)
{
	struct itimerval foo;

	foo.it_value.tv_sec =first/1000000;
	foo.it_value.tv_usec=first%1000000;
	foo.it_interval.tv_sec =others/1000000;
	foo.it_interval.tv_usec=others%1000000;
	setitimer(ITIMER_REAL, &foo, (struct itimerval *)0);
	return 0;
}
#else

static unsigned alarm_interval;

unsigned ualarm FUN2(unsigned,value, unsigned,interval)
{
	if(value==0 && interval==0)
		return alarm(0);
	alarm_interval=interval;
	return alarm(value);
}
#endif
#endif

/* This function is called when we get SIGALRM
   It just dumps timer_cells->fm_refs */
void
deal_alarm()
{
	int n;

#ifdef SYSV
	signal(SIGALRM, deal_alarm);
#endif
#if defined(NO_UALRM) && !defined(SETITIMER)
	alarm(alarm_interval);
#endif
#ifdef TEST
	if(!timer_cells || !timer_cells->refs_used)
		panic("No timer cells?");
#endif
	current_cycle++;
	for(n=0;n<timer_cells->refs_used;n++)
		push_cell(timer_cells->fm_refs[n].ref_row,timer_cells->fm_refs[n].ref_col);
}

/* All the timer_cells are going away, 'cuz everything is going away. . . */
void
flush_all_timers FUN0()
{
	if(timer_active) {
#ifdef SPLIT_REFS
		timer_cells->refs_used=0;
#else
		flush_fm_ref(timer_cells);
		timer_cells=0;
#endif
		timer_active=0;
#ifndef __TURBOC__
		ualarm(0,0);
#endif
	}
}

/* Add CUR_ROW, CUR_COL to the list of active timer-cells, turning on
   the timer_active, if it isn't already */
void
add_timer_ref FUN1(int,whereto)
{
	add_ref_to(whereto);
	add_ref_fm(&timer_cells,cur_row,cur_col);
	if(!timer_active) {
		timer_active++;
#ifndef __TURBOC__
		signal(SIGALRM,deal_alarm);
		ualarm(signal_ticks, signal_ticks);
#endif
	}
}

/* ---------- Routines and vars for dealing with the eval FIFO ------------ */
static struct cell_buf cell_buffer;

/* Start up the FIFO of cells to update */
void
init_refs FUN0()
{
	cell_buffer.size=FIFO_START;
	cell_buffer.buf=(struct pos *)ck_malloc(cell_buffer.size*sizeof(struct pos));
	bzero(cell_buffer.buf,cell_buffer.size*sizeof(struct pos));
	cell_buffer.push_to_here=cell_buffer.buf;
	cell_buffer.pop_frm_here=cell_buffer.buf;
	the_vars=hash_new();
}

/* Push the cells in REF onto the FIFO.  This calls push_cell to do the
   actual work. . . */
void
push_refs FUN1(struct ref_fm *, ref)
{
	int n;

	if(!ref || !ref->refs_used)
		return;
	n=ref->refs_used;
	while(n--) {
#ifdef TEST
		CELL *cp;

		if(debug&04)
			error_msg("Push %s",cell_name(ref->fm_refs[n].ref_row,ref->fm_refs[n].ref_col));
		cp=find_cell(ref->fm_refs[n].ref_row,ref->fm_refs[n].ref_col);
		if(cp->cell_cycle==current_cycle) {
			if(debug&01)
				error_msg("Cycle detected from %s to %s",
					cell_name(cur_row,cur_col),
					cell_name(ref->fm_refs[n].ref_row,ref->fm_refs[n].ref_col));
			push_cell(ref->fm_refs[n].ref_row,
				  ref->fm_refs[n].ref_col);
		} else
#endif
		push_cell(ref->fm_refs[n].ref_row,ref->fm_refs[n].ref_col);
	}
}

/* Push a cell onto the FIFO of cells to evaluate, checking for cells
   that are already on the FIFO, etc.

   This does not implement best-order recalculation, since there may be
   intersecting branches in the dependency tree, however, it's close enough
   for most people.
 */
void
push_cell FUN2(CELLREF,row, CELLREF,col)
{
	struct pos *dup;
	CELL *cp;
	struct ref_fm *rf;

	if(cell_buffer.push_to_here+1==cell_buffer.pop_frm_here || (cell_buffer.pop_frm_here==cell_buffer.buf && cell_buffer.push_to_here==cell_buffer.buf+(cell_buffer.size-1))) {
		int f, t, from_num;

		f=cell_buffer.pop_frm_here-cell_buffer.buf;
		t=cell_buffer.push_to_here-cell_buffer.buf;
		from_num=cell_buffer.size-f;

		cell_buffer.size FIFO_INC;

		cell_buffer.buf=(struct pos *)ck_realloc((VOIDSTAR )cell_buffer.buf,cell_buffer.size*sizeof(struct pos));
		if(t==0) {
			cell_buffer.push_to_here=cell_buffer.buf+f+from_num;
			cell_buffer.pop_frm_here=cell_buffer.buf+f;
		} else if(t>f) {
			cell_buffer.push_to_here=cell_buffer.buf+t;
			cell_buffer.pop_frm_here=cell_buffer.buf+f;
		} else {
			cell_buffer.push_to_here=cell_buffer.buf+t;
			cell_buffer.pop_frm_here=cell_buffer.buf+(cell_buffer.size-from_num);
			if(from_num)
				bcopy(	cell_buffer.buf+f,
					cell_buffer.pop_frm_here,
					from_num*sizeof(struct pos));
		}
	}

#if 1
	if(cell_buffer.pop_frm_here!=cell_buffer.push_to_here) {
		dup=cell_buffer.pop_frm_here;

		cp=find_cell(row,col);
		if(!cp) {
			return;
		}
		rf=cp->cell_refs_from;
		for(;dup!=cell_buffer.push_to_here;) {
			if(dup->row==row && dup->col==col) {
#ifdef TEST
				if(debug&010)
					error_msg("Flushed dup ref to %s",cell_name(row,col));
#endif
				*dup= *(cell_buffer.pop_frm_here);
				cell_buffer.pop_frm_here++;
				if(cell_buffer.pop_frm_here==cell_buffer.buf+cell_buffer.size)
					cell_buffer.pop_frm_here=cell_buffer.buf;
				break;
			}
#if 0
			if(rf) {
				for(n=0;n<rf->refs_used;n++)
					if(rf->fm_refs[n].ref_row==dup->row && rf->fm_refs[n].ref_col==dup->col) {
#ifdef TEST
						if(debug&01)
							error_msg("Swapped %s and %s",cell_name(row,col),cell_name(dup->row,dup->col));
#endif
						dup->row=row;
						dup->col=col;
						row=rf->fm_refs[n].ref_row;
						col-rf->fm_refs[n].ref_col;
						goto breakout;
					}
			}
#endif

			if(++dup==cell_buffer.buf+cell_buffer.size)
				dup=cell_buffer.buf;
		}
	}
#endif

	cell_buffer.push_to_here->row=row;
	cell_buffer.push_to_here->col=col;
	cell_buffer.push_to_here++;
	if(cell_buffer.push_to_here==cell_buffer.buf+cell_buffer.size)
		cell_buffer.push_to_here=cell_buffer.buf;
}

/* Pop a cell off CELL_BUFFER, and evaluate it, displaying the result. . .
   This returns 0 if there are no more cells to update, or if it gets
   an error. */

int
eval_next_cell FUN0()
{
	CELL *cp;
	static loop_counter = 40;

	if(cell_buffer.pop_frm_here==cell_buffer.push_to_here)
		return 0;
#ifdef TEST
	if(cell_buffer.pop_frm_here->row==NON_ROW || cell_buffer.pop_frm_here->col==NON_COL) {
		error_msg("Popping NON_CELL in eval_next_cell");
		return 0;
	}
#endif

	cur_row=cell_buffer.pop_frm_here->row;
	cur_col=cell_buffer.pop_frm_here->col;
	cell_buffer.pop_frm_here++;
	if(cell_buffer.pop_frm_here==cell_buffer.buf+cell_buffer.size)
		cell_buffer.pop_frm_here=cell_buffer.buf;

	cp=find_cell(cur_row,cur_col);
	if(cp) {
		if(cp->cell_cycle==current_cycle)
			--loop_counter;
		else
			loop_counter=40;
		update_cell(cp);
		pr_cell(cur_row,cur_col,cp);
		return loop_counter;
	} else {
#ifdef TEST
		error_msg("Can't update cell %s doesn't exist",cell_name(cur_row,cur_col));
#endif
		return 0;
	}
}

#ifdef TEST
void
cell_buffer_contents FUN0()
{
	struct pos *ptr;

	if(cell_buffer.pop_frm_here!=cell_buffer.push_to_here) {
		ptr=cell_buffer.pop_frm_here;
		for(;;) {
			printf("Ref to %s\r\n",cell_name(ptr->row,ptr->col));
			if(++ptr==cell_buffer.buf+cell_buffer.size)
				ptr=cell_buffer.buf;
			if(ptr==cell_buffer.push_to_here)
				break;
		}
	}
	printf("End of buffer\r\n");
}
#endif

/* ----------------- Routines for dealing with variables ------------------ */

/* This sets the variable V_NAME to V_NEWVAL
   It returns error msg, or 0 on success.
   All the appropriate cells have their ref_fm arrays adjusted appropriatly
   This could be smarter; when changing a range var, only the cells that
   were in the old value but not in the new one need their references flushed,
   and only the cells that are new need references added.
   This might also be changed to use add_range_ref()?
 */
char *
new_var_value FUN3(char *, v_name, int, v_namelen, char *,v_newval)
{
	struct var *var;
	int n;
	int newflag;
	struct rng tmp_rng;

	cur_row=MIN_ROW;
	cur_col=MIN_COL;
	if(v_newval && *v_newval) {
		n=parse_cell_or_range(&v_newval,&tmp_rng);
		if(!n)
			return "Can't parse cell or range";
		if(*v_newval)
			return "Junk after cell or range";
		newflag=((n|ROWREL|COLREL)==(R_CELL|ROWREL|COLREL)) ? VAR_CELL : VAR_RANGE;
	} else {
		tmp_rng.lr=tmp_rng.hr=NON_ROW;
		tmp_rng.lc=tmp_rng.hc=NON_COL;
		newflag= VAR_UNDEF;
	}

	var=find_or_make_var(v_name,v_namelen);

	if(var->var_ref_fm) {
		if(var->var_flags!=VAR_UNDEF) {
			for(n=0;n<var->var_ref_fm->refs_used;n++) {
				flush_range_ref(&(var->v_rng),
					var->var_ref_fm->fm_refs[n].ref_row,
					var->var_ref_fm->fm_refs[n].ref_col);
			}
		}
		var->v_rng = tmp_rng;

		if(var->v_rng.lr!=NON_ROW) {
			for(n=0;n<var->var_ref_fm->refs_used;n++) {
				cur_row=var->var_ref_fm->fm_refs[n].ref_row;
				cur_col=var->var_ref_fm->fm_refs[n].ref_col;
				add_range_ref(&(var->v_rng));
			}
		}
		for(n=0;n<var->var_ref_fm->refs_used;n++)
			push_cell(var->var_ref_fm->fm_refs[n].ref_row,
				var->var_ref_fm->fm_refs[n].ref_col);
	} else
		var->v_rng = tmp_rng;

	var->var_flags=newflag;

	return 0;
}

void
#ifdef __STDC__
for_all_vars(void (*func)())
#else
for_all_vars(func)
void (*func)();
#endif
{
	hash_apply(the_vars,func);
}

/* Find a variable in the list of variables, or create it if it doesn't
   exist.  Takes a name and a length so the name doesn't have to be
   null-terminated
 */
struct var *
find_or_make_var FUN2(char *, string, int, len)
{
	struct var *ret;
	int ch;

	ch=string[len];
	string[len]='\0';

	ret=hash_find(the_vars,string);
	if(ret) {
		string[len]=ch;
		return ret;
	}

	ret=(struct var *)ck_malloc(sizeof(struct var)+len);
	bcopy(string,ret->var_name,len+1);
	ret->var_flags=VAR_UNDEF;
	ret->v_rng.lr=0;
	ret->v_rng.lc=0;
	ret->v_rng.hr=0;
	ret->v_rng.hc=0;
	ret->var_ref_fm=0;
	hash_insert(the_vars,ret->var_name,ret);
	string[len]=ch;
	return ret;
}

/* Like find-or-make-var except returns 0 if it doesn't exist */
struct var *
find_var FUN2(char *, string, int, len)
{
	int ch;
	struct var *ret;

	ch=string[len];
	string[len]='\0';
	ret=hash_find(the_vars,string);
	string[len]=ch;
	return ret;
}

/* This adds a reference from CUR_ROW,CUR_COL to the variable VAR
   It calls add_ref or add_range_ref to have the cell(s) in VAR be
   referenced by CUR_ROW,CUR_COL
 */
void
add_var_ref FUN1(struct var *, var)
{
	add_ref_fm(&(var->var_ref_fm),cur_row, cur_col);
	switch(var->var_flags) {
	case VAR_UNDEF:
		break;
	case VAR_CELL:
		add_ref(var->v_rng.lr, var->v_rng.lc);
		break;
	case VAR_RANGE:
		add_range_ref(&(var->v_rng));
		break;
#ifdef TEST
	default:
		panic("Unknown var type %d in add_var_ref",var->var_flags);
#endif
	}
}

static void
flush_var FUN2(char *,name, struct var *,var)
{
#ifdef SPLIT_REFS
	if(var->var_ref_fm)
		free(var->var_ref_fm);
#endif
	free(var);
}


/* Free up all the variables, and (if SPLIT_REFS) the ref_fm structure
   associated with each variable.  Note that this does not get rid of
   the struct var *s in cell expressions, so it can only be used when all
   the cells are being freed also
 */
void
flush_variables FUN0()
{
	for_all_vars(flush_var);
	hash_die(the_vars);
	the_vars=hash_new();
}

