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