/*
 * $Id: ref.c,v 1.15 2001/02/13 23:38:06 danny Exp $
 *
 * Copyright © 1990, 1992, 1993, 2001 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 2, 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.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef	WITH_DMALLOC
#include <dstdlib.h>
#endif

#include "funcdef.h"
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <signal.h>
#include "sysdef.h"
#include "global.h"
#include "cell.h"
#include "eval.h"
#include "io-abstract.h"
#include "io-generic.h"
#include "hash.h"
#include "byte-compile.h"
#include "parse.h"
#include "ref.h"
#include "cmd.h"

static void add_ref_fm (struct ref_fm **, CELLREF, CELLREF);
static void flush_ref_fm (struct ref_fm **, CELLREF, CELLREF);
static void flush_range_ref (struct rng *, CELLREF, CELLREF);
extern void shift_formula (int r, int c, int dn, int ov);
static void flush_ref_to (struct ref_to **);
static void flush_fm_ref (struct ref_fm *);

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

int timer_active = 0;
struct ref_fm *timer_cells;

CELL *my_cell;

#ifdef TEST
extern int debug;
#endif

/* Functions for dealing exclusively with variables */
struct hash_control *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 (CELLREF row, CELLREF col, char *string)
{
  unsigned char *ret;

  cur_row = row;
  cur_col = col;

#ifdef TEST
  if (!string)
    {
      io_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;
}

extern int default_lock;

/* 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 (CELLREF row, CELLREF col, char *string)
{
  CELL *cp;

  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;
	}
      io_pr_cell (row, col, my_cell);
      my_cell = 0;
    }
  return 0;
}

char *
quote_new_value(CELLREF row, CELLREF col, char *string)
{
	int	l;
	char	*r, *s;

	l = strlen(string);

	s = (char *)malloc(l+3);
	s[0] = '"';
	s[1] = '\0';
	strcat(s, string);
	strcat(s, "\"");

	r = new_value(row, col, s);
	free(s);

	return r;
}

/* This sets the cell to a constant, stored in VALUE, whose type is in TYPE */
char *
set_new_value (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);
  io_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 (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;

	  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 (CELLREF rf, CELLREF cf, CELLREF rt, CELLREF ct)
{
  CELL *cpf;

  static CELLREF non_rf, non_cf;
  static struct cell non_cell;

  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.cell_format == 0)
		&& (non_cell.cell_flags.cell_precision == 0)
		&& (non_cell.cell_flags.cell_justify == 0)
		&& (non_cell.cell_flags.cell_type == 0))
		&& (non_cell.cell_flags.cell_lock == 0)
		&& !non_cell.cell_formula && !non_cell.cell_font)
	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->cell_font = non_cell.cell_font;
      my_cell->c_z = non_cell.c_z;
      push_refs (my_cell->cell_refs_from);
      if (my_cell->cell_refs_to)
	shift_formula (cur_row, cur_col, 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.cell_font = cpf->cell_font;
	  non_cell.c_z = cpf->c_z;
	bzero(&(cpf->cell_flags), sizeof(cpf->cell_flags));
	  cpf->cell_refs_to = 0;
	  cpf->cell_formula = 0;
	  cpf->cell_cycle = 0;
	  cpf->cell_font = 0;
	}
      return;
    }

  cur_row = rt;
  cur_col = ct;
  my_cell = find_cell (cur_row, cur_col);
  if ((!cpf ||
      (((non_cell.cell_flags.cell_format == 0)
		&& (non_cell.cell_flags.cell_precision == 0)
		&& (non_cell.cell_flags.cell_justify == 0)
		&& (non_cell.cell_flags.cell_type == 0))
		&& (non_cell.cell_flags.cell_lock == 0)
	&& !cpf->cell_formula && !cpf->cell_font))
      && !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->cell_font = cpf->cell_font;
  my_cell->c_z = cpf->c_z;

  bzero(&(cpf->cell_flags), sizeof(cpf->cell_flags));
  cpf->cell_refs_to = 0;
  cpf->cell_formula = 0;
  cpf->cell_cycle = 0;
  cpf->cell_font = 0;

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

/* Used only in regions.c for copy_region. */
void
copy_cell (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.cell_format == 0)
		&& (cpf->cell_flags.cell_precision == 0)
		&& (cpf->cell_flags.cell_justify == 0)
		&& (cpf->cell_flags.cell_type == 0))
		&& (cpf->cell_flags.cell_lock == 0)
	&& !cpf->cell_formula && !cpf->cell_font)) && !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;
  my_cell->cell_font = cpf->cell_font;
  my_cell->cell_refs_to = cpf->cell_refs_to;

  if (my_cell->cell_refs_to)
    my_cell->cell_refs_to->refs_refcnt++;

  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)
	{
	  unsigned char * refloc = fp - 1;
	  if (byte < USR1)
	    f = &the_funs[byte];
	  else if (byte < SKIP)
	    {
	      int tmp;
#ifdef TEST
	      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));
		  /* sparse array bug fixed here */
		  my_cell = find_cell (cur_row, cur_col);
		  cpf = find_cell (rf, cf);
		  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:
	      {
		struct function *fun;

		if (byte >= SKIP)
		  break;
		
		if (byte < USR1)
		  fun = &the_funs[byte];
		else
 		  fun = &usr_funs[byte - USR1][refloc[1]];
		
		if (fun->fn_comptype & C_T)
		  {
		    add_ref_fm (&timer_cells, rt, ct);
		    ++timer_active;
		  }
		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);
      if (len)
	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 (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);
	      /* sparse array bug fixed here */
	      my_cell = find_cell (cur_row, cur_col);
	      cpf = find_cell (rf, cf);
	    }
	}
      update_cell (my_cell);
    }
  else
    {
      my_cell->cell_formula = 0;
    }
  io_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 (void)
{
  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
		io_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 - USR1][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);
		    --timer_active;
		    break;
		  }
		else
		  io_error_msg ("Bad ref_to of %d.%x ignored", ref->to_refs[n], byte);
	      }
	      break;
	    }
	}
      flush_ref_to (&(my_cell->cell_refs_to));
    }
  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 ------ */


/* Record in the argument cell that cur_row/col depends on it. */

void
add_ref (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);
}

/* like add_ref, except over a range of arguments and with memory
 * management weirdness. 
 */
void
add_range_ref (struct rng *rng)
{
  CELL *other_cell;
  struct ref_fm *oldref, *newref;
  struct ref_fm nonref;


  make_cells_in_range (rng);

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

static void
flush_range_ref (struct rng *rng, CELLREF rr, CELLREF cc)
{
  CELL *other_cell;
  struct ref_fm *oldref, *newref;
  struct ref_fm nonref;
  /* 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);
  /* 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 ();
  if (!other_cell)
    return;
  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);
    }
}

#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 (void)
{
  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 (void)
{
  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;
  if (tmp->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 (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)
	    {
	      io_error_msg ("Old not in refs_list in flush_fm_ref(%p)", old);
	      return;
	    }
#endif
	  if (tmp)
	    tmp->refs_next = old->refs_next;
	}
      free (old);
    }
}

/* This adds a from reference to a cells reference list.
 * Note that the ref_fm structures themselves are hash-consed.
 */
static void
add_ref_fm (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 (struct ref_fm **where, CELLREF r, CELLREF c)
{
  struct ref_fm *from;
  int n;

  from = *where;
#ifdef TEST
  if (!from)
    {
      io_error_msg ("No refs in flush_ref_fm(%p,%u,%u)", where, r, c);
      return;
    }
#endif
  if (!from)
    return;
  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)
    {
      io_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

void
dbg_print_ref_fm (rf)
     struct ref_fm *rf;
{
  int nr;
  char *bufp;
  extern char *print_buf;

  if (rf)
    {
      io_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)
	    {
	      io_text_line (print_buf);
	      bufp = print_buf;
	    }
	  else
	    bufp += strlen (bufp);
	}
      if (nr % 10)
	io_text_line (print_buf);
    }
}

#endif

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

  /* io_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)
    {
      /* io_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))
	{
	  /* io_error_msg("Hit!"); */
	  tmp->refs_refcnt++;
	  return tmp;
	}
#ifdef TEST
      else
	to_misses++;
#endif
    }

  /* io_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;
  if (tmp->refs_used)
    bcopy (to_tmp_ref->to_refs, tmp->to_refs, tmp->refs_used);

  return tmp;
}

void
add_ref_to (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 (struct ref_to **where)
{
  struct ref_to *tmp;
  struct ref_to *old;
  int n;
  unsigned long hash;

#ifdef TEST
  if (!where || !*where)
    {
      io_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)
	    {
	      io_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 (rt, form)
     struct ref_to *rt;
     unsigned char *form;
{
  int nr;
  char *bufp;
  extern char *print_buf;

  if (rt)
    {
      io_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)
	    {
	      io_text_line (print_buf);
	      bufp = print_buf;
	    }
	  else
	    bufp += strlen (bufp);
	}
      if (nr % 7)
	io_text_line (print_buf);
    }
}

void
ref_stats ()
{
  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;
    }
  io_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);
  io_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

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

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

/* This removes all the CELL_REF_FM links associated with a 
 * variable, and adjusts the variables value.
 * After calling this function, one must also call 
 * finish_shift_var to install the new CELL_REF_FM links.
 */
static void 
start_shift_var (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? */
      io_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 */

    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;
      v->var_flags = VAR_DANGLING_RANGE;
    }
}


static void 
finish_shift_var (char *name, struct var *v)
{
  int n;
  if (v->var_flags != VAR_DANGLING_RANGE)
    return;
  
  v->var_flags = VAR_RANGE;

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

#define RIGHT	8
#define LEFT	4
#define BOTTOM	2
#define TOP	1

/*
 * This iterates over the region FM, preparing the cells there to be shifted
 * OV(er) and D(ow)N.
 *
 * After this, the ref_fm and ref_to lists of a cell within the region should
 * be appropriate to the location that cell will be shifted to.
 * 
 * Variables and references to variables are also shifted.
 */
void
shift_outside (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 rng orng;

  char *ptr;
  unsigned long val;
  static char DEF_REF[] = "DEFREF";
  static char DEF_RNG[] = "DEFRNG";

  /* Variables and references to variables are also shifted. */
  shift_fm = fm;
  shift_dn = dn;
  shift_ov = ov;
  for_all_vars (start_shift_var);

  /* This stack is used to defer adjustments to references that are entirely 
   * within FM.  Intra-FM references are adjusted after references into and
   * out of FM.
   */

  if (!moving)
    moving = init_stack ();

  find_cells_in_range (fm);

  while ((cp = next_row_col_in_range (&rr, &cc)))
    {
      /* cp/rr/cc is a cell in FM. */

      /* First, adjust references FROM 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/cc/cp is the cell being referenced */
		  trr = GET_ROW (fp + 1);
		  tcc = GET_COL (fp + 1);
		  tcp = find_cell (trr, tcc);

		  /* Get rid of the backpointer to this reference. */
		  flush_ref_fm (&(tcp->cell_refs_from), rr, cc);

		  /* frr/fcc is the new address of the referenced cell.
		   * The address will change if Trr/cc happens to be 
	   	   * in the region that is moving, or if the reference
		   * is a relative reference.
		   */
		  fn = (BETWEEN (trr, fm->lr, fm->hr)
			&& BETWEEN (tcc, fm->lc, fm->hc)); 
		  frr = (fn || (((*fp) & ROWREL))) ? trr + dn : trr;
		  fcc = (fn || (((*fp) & COLREL))) ? tcc + ov : tcc;

		  PUT_ROW (fp + 1, frr); /* Adjust the formula byte-code. */
		  PUT_COL (fp + 1, fcc); /* This might even be a noop. */

		  /* Reinstall the backreference, unless the new address of the
		   * referenced cell is w/in the region being moved. (In which
		   * case, defer making the backreference).
		   */
		  if (BETWEEN(frr, fm->lr, fm->hr) && BETWEEN(fcc, fm->lc, fm->hc))
		    {
		      push_stack (moving, (VOIDSTAR) TO_MAGIC (rr + dn, cc + ov));
		      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 VAR:
		  {
		    struct var * varp;
		    bcopy ((VOIDSTAR) (fp + 1),
			   (VOIDSTAR)&varp, sizeof (struct var *));
		    flush_ref_fm (&varp->var_ref_fm, rr, cc);
		    add_ref_fm (&varp->var_ref_fm, rr + dn, cc + ov);
		    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:
		  /* orng is the range being referenced. */
		  GET_RNG (fp + 1, &orng);

		  /* Get rid of backpointers to this reference. */
		  flush_range_ref (&orng, rr, cc);

				  /* This asks -- does the referenced region
			   	   * intersect the region being moved at the: 
				   */
		  fn = ((BETWEEN (orng.hc, fm->lc, fm->hc) << 3)   /* right?  8 */
		       | (BETWEEN (orng.lc, fm->lc, fm->hc) << 2)  /* left?   4 */
		       | (BETWEEN (orng.hr, fm->lr, fm->hr) << 1)  /* bottom? 2 */
		       | BETWEEN (orng.lr, fm->lr, fm->hr));       /* top?    1 */

		  /* In this switch, a union of masks represents a conjunction 
		   * of intersections.  So, LEFT | TOP means `interects at left
		   * and top'. 
		   */
		  switch (fn)
		    {
		      /* Most of the time, the referenced region is moved only
		       * if the reference is relative.
    		       */
		    case LEFT | TOP:
		    case LEFT | BOTTOM:
		    case RIGHT | TOP:
		    case RIGHT | BOTTOM:

		      /* There used to be a warning given to the user here, but
		       * that seems silly, no? 
		       */

		    case 0:
		    case TOP:
		    case BOTTOM:
		    case TOP | BOTTOM:
		    case LEFT:
		    case RIGHT:
		    case LEFT | RIGHT:
		      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;

		      /* If the referenced range contains rows or columns that
		       * are entirely within the region being moved, then 
		       * the region is moved, shrunk or stretched.
		       */
		    case LEFT | BOTTOM | TOP:
		    case RIGHT | BOTTOM | TOP:
		    case RIGHT | LEFT | TOP:
		    case RIGHT | LEFT | BOTTOM:
		    case RIGHT | LEFT | BOTTOM | TOP:
		      if (fn != (LEFT | BOTTOM | TOP))
			orng.hc += ov;
		      if (fn != (RIGHT | BOTTOM | TOP))
			orng.lc += ov;
		      if (fn != (RIGHT | LEFT | TOP))
			orng.hr += dn;
		      if (fn != (RIGHT | LEFT | BOTTOM))
			orng.lr += dn;
		      break;
		    }
		  PUT_RNG (fp + 1, &orng); /* Patch the bytecode. */

		  push_stack (moving, (VOIDSTAR) fp);
		  push_stack (moving, (VOIDSTAR) TO_MAGIC (rr + dn, cc + ov));
		  push_stack (moving, DEF_RNG);
		  break;

		default:
		  {
		    struct function *fun;

		    if (*fp < USR1)
		      fun = &the_funs[*fp];
		    else
		      fun = &usr_funs[*fp - USR1][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;
		}

	    }
	}

      /* Next, adjust references TO the region */
      for (n = 0; cp->cell_refs_from && n < cp->cell_refs_from->refs_used; n++)
	{
	  /* The second enclosed loop over the bytecode will fix all of the
	   * references to this cell.  This loop is here because a 
	   * refs_fm structure may contain more than one occurence of the
	   * referencing cell.  We don't want to adjust the same bytecode
	   * twice.
	   */
	  while ((n < cp->cell_refs_from->refs_used - 1)
		 && (cp->cell_refs_from->fm_refs[n].ref_row == 
		     cp->cell_refs_from->fm_refs[n + 1].ref_row)
		 && (cp->cell_refs_from->fm_refs[n].ref_col == 
		     cp->cell_refs_from->fm_refs[n + 1].ref_col))
	    ++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 the reference is from inside the region we're moving, in 
	   * which case, it has already been adjusted.
	   * 
	   * (This test seems unnecessary but harmless. -tl)
	   */
	  if (BETWEEN (frr, fm->lr, fm->hr) && BETWEEN (fcc, fm->lc, fm->hc))
	    continue;

	  /* Find the cell that references cp. */
	  fcp = find_cell (frr, fcc);

	  /* Search the byte-code for the reference. */
	  for (fn = 0; fcp->cell_refs_to && 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)
		    continue;

		  {
		    CELLREF old_tr = trr;
		    CELLREF old_tc = tcc;
		    /* Find the cell that fcp should reference now. */
		    if (!((*ffp) & ROWREL))
		      {
			trr += dn;
			PUT_ROW (ffp + 1, trr);
		      }
		    if (!((*ffp) & COLREL))
		      {
			tcc += ov;
			PUT_COL (ffp + 1, tcc);
		      }
		    else
		      /* If this is an abs reference, it doesn't change. */
		      continue;
		    /* Get rid of the now-invalid backpointer */
		    {
		      CELL * old_reffed = find_cell (old_tr, old_tc);
		      flush_ref_fm (&(old_reffed->cell_refs_from), frr, fcc);
		    }
		  }

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


		case VAR:
		  /* This case is taken care of by {start,finish}_shift_vars */
		  continue;

		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) /* right */
			 + (BETWEEN (orng.lc, fm->lc, fm->hc) << 2) /* left */
			 + (BETWEEN (orng.hr, fm->lr, fm->hr) << 1) /* bottom */
			 + BETWEEN (orng.lr, fm->lr, fm->hr)); /* top */

		  /* If the reference is absolute, or relative only in directions
		   * that aren't changing, there's nothing to do. 
		   */
		  if (!(*ffp == RANGE
			|| (!dn && ((*ffp) | LRREL | HRREL) == (RANGE | LRREL | HRREL))
			|| (!ov && ((*ffp) | LCREL | HCREL) == (RANGE | LCREL | HCREL))))
		    continue;

		  /* If it's a case we don't know how to adjust, there's
		   * nothing to do.  If there is no overlap, there's nothing to
		   * do.
		   */
		  if ((val != (LEFT | BOTTOM | TOP))
		      && (val != (RIGHT | BOTTOM | TOP))
		      && (val != (RIGHT | LEFT | TOP))
		      && (val != (RIGHT | LEFT | BOTTOM))
		      && (val != (RIGHT | LEFT | BOTTOM | TOP)))
		    continue;
		  
		  flush_range_ref (&orng, frr, fcc);

		  if (val != (RIGHT | LEFT | BOTTOM))
		    orng.lr += dn;
		  if (val != (RIGHT | LEFT | TOP))
		    orng.hr += dn;
		  if (val != (RIGHT | BOTTOM | TOP))
		    orng.lc += ov;
		  if (val != (LEFT | BOTTOM  | TOP))
		    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);
		  continue;
#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) io_error_msg
		      ("Unknown byte (%d) for reference_to #%d %d",
		       *ffp, fn, fcp->cell_refs_to->to_refs[fn]);
		  }
		  break; 
#endif
		}
	    }
	}
    }

  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);
	  if (my_cell)
	    panic ("shift_outside: my_cell lost.");
	  /* If this panic occurs, the caller should be recomputing
	   * my_cell after shift_outside returns (and this useful panic
	   * will have to be removed or my_cell set temporarily to 0).
	   */
	}
#ifdef TEST
      else
	panic ("Now what (%p)?", ptr);
#endif
    }
  for_all_vars (finish_shift_var);
  /* 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 (int r, int c, 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);
	    /* sparse array bug fixed here */
	    my_cell = find_cell (r, c);
	  }
	  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));
		/* sparse array bug fixed here */
		my_cell = find_cell (r, c);
		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 -------------- */


/* This function is called when the alarm has gone off (but not from inside
 * the signal handler!). It schedules timer_cells->fm_refs for recalc. 
 */
void
cell_alarm (void)
{
  int n;
  static time_t last_time = 0;
  if (timer_active)
    {
      time_t this_time = time(0);
      if ((this_time - last_time) < cell_timer_seconds)
	return;
      last_time = this_time;
      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 (void)
{
  if (timer_active)
    {
      flush_fm_ref (timer_cells);
      timer_cells = 0;
      timer_active = 0;
    }
}

/* 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 (int whereto)
{
  add_ref_to (whereto);
  add_ref_fm (&timer_cells, cur_row, cur_col);
  ++timer_active;
}

/* ---------- 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 (void)
{
  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 (struct ref_fm *ref)
{
  int n;

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

      fprintf (stderr, "Push %s\n", 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)
	{
	  fprintf (stderr, "Cycle detected from %s to %s\n",
			  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.
 */
static void cell_buffer_contents (FILE *fp);

void
push_cell (CELLREF row, CELLREF col)
{
  struct pos *dup;
  CELL *cp;
  struct ref_fm *rf;

  /* printf("push_cell entry %d %d\n", row, col); cell_buffer_contents(stdout); */
  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)
		io_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)
		      io_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;
  /* printf("push_cell exit %d %d\n", row, col); cell_buffer_contents(stdout); */
}

/* 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 (void)
{
  CELL *cp;
  static int loop_counter = 40;

  if (cell_buffer.pop_frm_here == cell_buffer.push_to_here)
    return 0;

  cur_row = cell_buffer.pop_frm_here->row;
  cur_col = cell_buffer.pop_frm_here->col;
  /* printf("eval_next_cell %d %d\n", cur_row, cur_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;

  if (!(cp = find_cell(cur_row, cur_col)))
    return 0;

  if (cp->cell_cycle == current_cycle)
    --loop_counter;
  else
    loop_counter = 40;

#if 0
fprintf(stderr, "eval_next_cell:  cp->cell_cycle = %d, current_cycle = %d, loop_counter = %d\n", cp->cell_cycle, current_cycle, loop_counter);
  cell_buffer_contents(stderr);
#endif
  update_cell (cp);
  io_pr_cell (cur_row, cur_col, cp);
#if 0
  if (!loop_counter)
    printf("eval_next_cell: returning 0 due to loop_counter\n");
#endif
  return loop_counter;
}

#if 1
static void
cell_buffer_contents (FILE *fp)
{
  struct pos *ptr;

  if (!fp)
    fp = stdout;
  if (cell_buffer.pop_frm_here != cell_buffer.push_to_here)
    {
      ptr = cell_buffer.pop_frm_here;
      for (;;)
	{
	  /* fprintf (fp, "Ref to %s\r\n", cell_name (ptr->row, ptr->col)); */
	  fprintf (fp, " -> %d %d\n", ptr->row, ptr->col);
	  if (++ptr == cell_buffer.buf + cell_buffer.size)
	    ptr = cell_buffer.buf;
	  if (ptr == cell_buffer.push_to_here)
	    break;
	}
    }
  /* fprintf (fp, "End of buffer\r\n"); */
}

#endif

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

/* Either this needs to be redone as a wrapper for the new new_var_value,
 * or the invocations of it in oleofile.c, sc.c and sylk.c need to be
 * adjusted so it can be deleted altogether.  The new version has 
 * been introduced to improve the interface of new set-var.  The
 * only cost has been the need to introduce and unset-var command,
 * since the '@' argument type now required by set-var will
 * only recognize a valid region.
 */
char *
old_new_var_value (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;
}

/* 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 appropriately
 * 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 (char *v_name, int v_namelen, struct rng *rng)
{
  struct var *var;
  int n = 0;
  int newflag = 0;

  cur_row = MIN_ROW;
  cur_col = MIN_COL;

  newflag = ((ROWREL | COLREL) == (R_CELL | ROWREL | COLREL)) ? VAR_CELL : VAR_RANGE;

  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 = *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 = *rng;

  var->var_flags = newflag;

  return 0;
}

void
for_all_vars (void (*func) (char *, struct var *))
{
  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 (char *string, int len)
{
  struct var *ret;
  int ch;

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

  ret = (struct var *) 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 (char *string, int len)
{
  int ch;
  struct var *ret;

  ch = string[len];
  string[len] = '\0';
  ret = (struct var *) 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 (void * vvar)
{
  struct var *var = (struct var *)vvar;
  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 (char *name, struct var *var)
{
  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 (void)
{
  for_all_vars (flush_var);
  hash_die (the_vars);
  the_vars = hash_new ();
}


syntax highlighted by Code2HTML, v. 0.9.1