teapot-spreadsheet/sheet.c
Glen Whitney 08b42bf424 Prevent phantom values when clocking, resetting, and clocking again
In the end it turned out that the cause of the phantom values was
  short-cutting in getvalue() when the contents of a cell were empty,
  preventing the update of the internal cache of the value of the cell.

  However, tracking this down (and getting the associated memory management
  correct) necessitated implementing a debugging mode in which I could
  dump the internal states of cells and print various other stuff to standard
  output. It also involved understanding the meaning of various pointers in
  the code, in the process of which I renamed some commonly used macros,
  particularly the former SHEET(s,x,y,z) which was not returning a Sheet at
  all but rather a pointer to a Cell. So this macro is now called CELL_AT. I
  also replaced several very repeatedly used patterns of checking the validity
  of locations and pointers with macros, now defined in sheet.h.

  Therefore, unfortunately the (relatively small in the end) bugfix for this
  major issue is entangled with numerous textual changes to the code made
  in tracking it down.
  Fixes #18.
  Closes #19.
2019-07-24 10:47:39 -07:00

2103 lines
56 KiB
C

/* #includes */ /*{{{C}}}*//*{{{*/
#ifdef DMALLOC
#include "dmalloc.h"
#endif
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "csv.h"
#include "default.h"
#include "display.h"
#include "eval.h"
#include "main.h"
#include "misc.h"
#include "parser.h"
#include "scanner.h"
#include "sheet.h"
#include "utf8.h"
#include "xdr.h"
/*}}}*/
/* #defines */ /*{{{*/
#define HASH(x,s) \
{ \
const unsigned char *S=(const unsigned char*)s; \
\
x=0; \
while (*S) { x=(x<<5)+((x>>27)^*S); ++S; } \
x%=LABEL_CACHE; \
}
/*}}}*/
/* variables */ /*{{{*/
static int upd_clock; /* evaluate clocked expressions */
/* Used during evaluation of a cell to specify the currently updated cell */
Sheet *upd_sheet;
int upd_x;
int upd_y;
int upd_z;
int max_eval;
/*}}}*/
/* copytokens -- copy a sequence of tokens, possibly reallocating dest */ /*{{{*/
static void copytokens(Token*** totoks, Token** fromtoks)
{
/* variables */ /*{{{*/
size_t from_len, i;
/*}}}*/
from_len = tveclen(fromtoks);
if (from_len == 0)
{
tvecfree(*totoks);
*totoks = EMPTY_TVEC;
return;
}
from_len;
if (from_len > tveclen(*totoks))
{
tvecfree(*totoks);
*totoks = malloc((from_len+1)*sizeof(Token*));
(*totoks)[from_len] = NULLTOKEN;
} else {
tvecfreetoks(*totoks);
}
for (i=0; i<from_len; ++i) /* destination already has NULLTOKEN at end */
{
if (fromtoks[i] == NULLTOKEN) (*totoks)[i] = NULLTOKEN;
else
{
(*totoks)[i] = malloc(sizeof(Token));
*((*totoks)[i]) = tcopy(*(fromtoks[i]));
}
}
}
/*}}}*/
/* copycell -- copy a cell */ /*{{{*/
static void copycell(Sheet *sheet1, int x1, int y1, int z1, Sheet *sheet2, int x2, int y2, int z2)
{
/* variables */ /*{{{*/
Cell *fromcell, *tocell;
Token **run;
int i;
/*}}}*/
assert(sheet1!=(Sheet*)0);
assert(sheet2!=(Sheet*)0);
if (LOC_WITHIN(sheet1,x1,y1,z1))
{
sheet2->changed=1;
fromcell = CELL_AT(sheet1,x1,y1,z1);
if (fromcell == NULLCELL) freecell(sheet2,x2,y2,z2);
else
/* copy first cell to second */ /*{{{*/
{
freecell(sheet2,x2,y2,z2);
initcell(sheet2,x2,y2,z2);
tocell = CELL_AT(sheet2,x2,y2,z2);
memcpy(tocell, fromcell, sizeof(Cell));
copytokens(&(tocell->contents), fromcell->contents);
copytokens(&(tocell->ccontents), fromcell->ccontents);
if (fromcell->label != (char*)0)
tocell->label = strcpy(malloc(strlen(fromcell->label)+1),
fromcell->label);
tocell->value.type=EMPTY;
}
/*}}}*/
}
else freecell(sheet2,x2,y2,z2);
}
/*}}}*/
/* dump_cell -- write internal contents of cell to standard out */ /*{{{*/
static void dump_cell(Sheet *sheet, int x, int y, int z)
{
Cell* c;
char buf[2048];
assert(sheet != (Sheet*)0);
if (x < 0 || y < 0 || z < 0)
{
printf("TEADUMP: Requested cell &(%d,%d,%d) has negative coordinates.\n",
x, y, z);
return;
}
if (!LOC_WITHIN(sheet, x, y, z))
{
printf("TEADUMP: Requested cell &(%d,%d,%d) outside sheet dims %d,%d,%d.\n",
x, y, z, sheet->dimx, sheet->dimy, sheet->dimz);
return;
}
c = CELL_AT(sheet, x, y, z);
if (c == NULLCELL)
{
printf("TEADUMP: Cell at &(%d,%d,%d) is empty.\n", x, y, z);
return;
}
printf("TEADUMP of &(%d,%d,%d):\n", x, y, z);
print(buf, sizeof(buf), 0, 1, 0, -1, c->contents);
printf(" Base expr: %s.\n", buf);
print(buf, sizeof(buf), 0, 1, 0, -1, c->ccontents);
printf(" Update expr: %s.\n", buf);
if (c->label == (char *)0) printf("\n No label.\n");
else printf("\n Label: %s.\n", c->label);
printtok(buf, sizeof(buf), 0, 1, 0, -1, 1, &(c->value));
printf(" Current value: %s.\n", buf);
printtok(buf, sizeof(buf), 0, 1, 0, -1, 1, &(c->resvalue));
printf(" Stored result value: %s.\n Adjustment: ", buf);
switch (c->adjust) {
case LEFT: printf("LEFT\n"); break;
case RIGHT: printf("RIGHT\n"); break;
case CENTER: printf("CENTER\n"); break;
case AUTOADJUST: printf("AUTO\n"); break;
}
printf(" Precision: %d\n Attributes: ", c->precision);
if (c->updated) printf("updated ");
if (c->shadowed) printf("shadowed ");
if (c->scientific) printf("scientific ");
if (c->locked) printf("locked ");
if (c->transparent) printf("transparent ");
if (c->ignored) printf("ignored ");
if (c->clock_t0) printf("clock_t0 ");
if (c->clock_t1) printf("clock_t1 ");
if (c->clock_t2) printf("clock_t2 ");
if (c->bold) printf("bold ");
if (c->underline) printf("underline ");
printf("\n\n");
}
/*}}}*/
/* dump_current_cell -- dump_cell of the current_cell */ /*{{{*/
void dump_current_cell(Sheet *sheet)
{
assert(sheet != (Sheet *)0);
dump_cell(sheet, sheet->curx, sheet->cury, sheet->curz);
}
/*}}}*/
/* swapblock -- swap two non-overlapping blocks of cells */ /*{{{*/
static void swapblock(Sheet *sheet1, int x1, int y1, int z1, Sheet *sheet2, int x2, int y2, int z2, int xdist, int ydist, int zdist)
{
int xoff, yoff, zoff;
assert(sheet1!=(Sheet*)0);
assert(sheet2!=(Sheet*)0);
resize(sheet1,x1+xdist-1,y1+ydist-1,z1+zdist-1);
resize(sheet2,x2+xdist-1,y2+ydist-1,z2+zdist-1);
for (xoff=0; xoff<xdist; ++xoff)
for (yoff=0; yoff<ydist; ++yoff)
for (zoff=0; zoff<zdist; ++zoff)
{
Cell *t;
t = CELL_AT(sheet1,x1+xoff,y1+yoff,z1+zoff);
CELL_AT(sheet1,x1+xoff,y1+yoff,z1+zoff) = CELL_AT(sheet2,x2+xoff,y2+yoff,z2+zoff);
CELL_AT(sheet2,x2+xoff,y2+yoff,z2+zoff) = t;
}
sheet1->changed=1;
sheet2->changed=1;
}
/*}}}*/
/* cmpcell -- compare to cells with given order flags */ /*{{{*/
/* Notes */ /*{{{*/
/*
Compare the _values_ of two cells. The result is -1 if first is smaller
than second, 0 if they are equal and 1 if the first is bigger than the
second. A result of 2 means they are not comparable.
*/
/*}}}*/
static int cmpcell(Sheet *sheet1, int x1, int y1, int z1, Sheet *sheet2, int x2, int y2, int z2, int sortkey)
{
Cell *leftcell, *rightcell;
assert(sheet1!=(Sheet*)0);
assert(sheet2!=(Sheet*)0);
/* empty cells are smaller than any non-empty cell */ /*{{{*/
if (!CELL_IS_GOOD(sheet1,x1,y1,z1) || CELL_AT(sheet1,x1,y1,z1)->value.type==EMPTY)
{
if (!CELL_IS_GOOD(sheet2,x2,y2,z2) || CELL_AT(sheet2,x2,y2,z2)->value.type==EMPTY) return 0;
else return (sortkey&ASCENDING ? -1 : 1);
}
if (!CELL_IS_GOOD(sheet2,x2,y2,z2) || CELL_AT(sheet2,x2,y2,z2)->value.type==EMPTY) return (sortkey&ASCENDING ? 1 : -1);
/*}}}*/
leftcell = CELL_AT(sheet1,x1,y1,z1);
rightcell = CELL_AT(sheet2,x2,y2,z2);
switch (leftcell->value.type)
{
/* STRING */ /*{{{*/
case STRING:
{
if (rightcell->value.type==STRING)
{
int r;
r=strcmp(leftcell->value.u.string, rightcell->value.u.string);
if (r<0) return (sortkey&ASCENDING ? -1 : 1);
else if (r==0) return 0;
else return (sortkey&ASCENDING ? 1 : -1);
}
return 2;
}
/*}}}*/
/* FLOAT */ /*{{{*/
case FLOAT:
{
if (rightcell->value.type==FLOAT)
{
if (leftcell->value.u.flt<rightcell->value.u.flt) return (sortkey&ASCENDING ? -1 : 1);
else if (leftcell->value.u.flt==rightcell->value.u.flt) return 0;
else return (sortkey&ASCENDING ? 1 : -1);
}
if (rightcell->value.type==INT)
{
if (leftcell->value.u.flt<rightcell->value.u.integer) return (sortkey&ASCENDING ? -1 : 1);
else if (leftcell->value.u.flt==rightcell->value.u.integer) return 0;
else return (sortkey&ASCENDING ? 1 : -1);
}
return 2;
}
/*}}}*/
/* INT */ /*{{{*/
case INT:
{
if (rightcell->value.type==INT)
{
if (leftcell->value.u.integer<rightcell->value.u.integer) return (sortkey&ASCENDING ? -1 : 1);
else if (leftcell->value.u.integer==rightcell->value.u.integer) return 0;
else return (sortkey&ASCENDING ? 1 : -1);
}
if (rightcell->value.type==FLOAT)
{
if (leftcell->value.u.integer<rightcell->value.u.flt) return (sortkey&ASCENDING ? -1 : 1);
else if (leftcell->value.u.integer==rightcell->value.u.flt) return 0;
else return (sortkey&ASCENDING ? 1 : -1);
}
return 2;
}
/*}}}*/
default: return 2;
}
}
/*}}}*/
/* resize -- check if sheet needs to be resized in any dimension */ /*{{{*/
void resize(Sheet *sheet, int x, int y, int z)
{
assert(x>=0);
assert(y>=0);
assert(z>=0);
assert(sheet!=(Sheet*)0);
if (!LOC_WITHIN(sheet,x,y,z))
{
/* variables */ /*{{{*/
Cell **newsheet;
int *newcolumn;
unsigned int ndimx,ndimy,ndimz;
/*}}}*/
sheet->changed=1;
ndimx=(x>=sheet->dimx ? x+1 : sheet->dimx);
ndimy=(y>=sheet->dimy ? y+1 : sheet->dimy);
ndimz=(z>=sheet->dimz ? z+1 : sheet->dimz);
/* allocate new sheet */ /*{{{*/
newsheet=malloc(ndimx*ndimy*ndimz*sizeof(Cell*));
for (x=0; x<ndimx; ++x) for (y=0; y<ndimy; ++y) for (z=0; z<ndimz; ++z)
{
if (LOC_WITHIN(sheet,x,y,z)) *(newsheet+x*ndimz*ndimy+y*ndimz+z)=CELL_AT(sheet,x,y,z);
else *(newsheet+x*ndimz*ndimy+y*ndimz+z)= NULLCELL;
}
if (sheet->sheet!=(Cell**)0) free(sheet->sheet);
sheet->sheet=newsheet;
/*}}}*/
/* allocate new columns */ /*{{{*/
if (x>sheet->dimx || z>=sheet->dimz)
{
newcolumn=malloc(ndimx*ndimz*sizeof(int));
for (x=0; x<ndimx; ++x) for (z=0; z<ndimz; ++z)
{
if (x<sheet->dimx && z<sheet->dimz) *(newcolumn+x*ndimz+z)=*(sheet->column+x*sheet->dimz+z);
else *(newcolumn+x*ndimz+z)=DEF_COLUMNWIDTH;
}
if (sheet->column!=(int*)0) free(sheet->column);
sheet->column=newcolumn;
}
/*}}}*/
sheet->dimx=ndimx;
sheet->dimy=ndimy;
sheet->dimz=ndimz;
}
}
/*}}}*/
/* initcell -- initialise new cell, if it does not exist yet */ /*{{{*/
void initcell(Sheet *sheet, int x, int y, int z)
{
Cell *nc;
assert(x>=0);
assert(y>=0);
assert(z>=0);
resize(sheet,x,y,z);
if (CELL_AT(sheet,x,y,z) == NULLCELL)
{
sheet->changed=1;
nc = malloc(sizeof(Cell));
CELL_AT(sheet,x,y,z) = nc;
nc->contents = EMPTY_TVEC;
nc->ccontents = EMPTY_TVEC;
nc->label=(char*)0;
nc->adjust=AUTOADJUST;
nc->precision=-1;
nc->shadowed=0;
nc->bold=0;
nc->underline=0;
nc->scientific=DEF_SCIENTIFIC;
nc->value.type=EMPTY;
nc->resvalue.type=EMPTY;
nc->locked=0;
nc->ignored=0;
nc->clock_t0=0;
nc->clock_t1=0;
nc->clock_t2=0;
}
}
/*}}}*/
/* cachelabels -- create new label cache */ /*{{{*/
void cachelabels(Sheet *sheet)
{
int i,x,y,z;
if (sheet==(Sheet*)0) return;
for (i=0; i<LABEL_CACHE; ++i) /* free bucket */ /*{{{*/
{
struct Label *run;
for (run=sheet->labelcache[i]; run!=(struct Label*)0;)
{
struct Label *runnext;
runnext=run->next;
free(run);
run=runnext;
}
sheet->labelcache[i]=(struct Label*)0;
}
/*}}}*/
for (ALL_CELLS_IN_SHEET(sheet,x,y,z))
/* cache all labels */ /*{{{*/
{
const char *l;
l=getlabel(sheet,x,y,z);
if (*l)
{
unsigned long hx;
struct Label **run;
HASH(hx,l);
for (run=&sheet->labelcache[(unsigned int)hx]; *run!=(struct Label*)0 && strcmp(l,(*run)->label); run=&((*run)->next));
if (*run==(struct Label*)0)
{
*run=malloc(sizeof(struct Label));
(*run)->next=(struct Label*)0;
(*run)->label=l;
(*run)->x=x;
(*run)->y=y;
(*run)->z=z;
}
/* else we have a duplicate label, which _can_ happen under */
/* unfortunate conditions. Don't tell anybody. */
}
}
/*}}}*/
}
/*}}}*/
/* freesheet -- free all cells of an entire spread sheet */ /*{{{*/
void freesheet(Sheet *sheet, int all)
{
/* variables */ /*{{{*/
int x,y,z;
/*}}}*/
assert(sheet!=(Sheet*)0);
sheet->changed=0;
for (ALL_CELLS_IN_SHEET(sheet,x,y,z))
{
freecell(sheet,x,y,z);
}
if (all)
{
int i;
for (i=0; i<LABEL_CACHE; ++i) /* free all buckets */ /*{{{*/
{
struct Label *run;
for (run=sheet->labelcache[i]; run!=(struct Label*)0;)
{
struct Label *runnext;
runnext=run->next;
free(run);
run=runnext;
}
}
/*}}}*/
if (sheet->sheet) free(sheet->sheet);
if (sheet->column) free(sheet->column);
if (sheet->name) free(sheet->name);
}
else
{
for (x=0; x<sheet->dimx; ++x) for (z=0; z<sheet->dimz; ++z)
{
*(sheet->column+x*sheet->dimz+z)=DEF_COLUMNWIDTH;
}
cachelabels(sheet);
forceupdate(sheet);
}
}
/*}}}*/
/* forceupdate -- clear all clock and update flags */ /*{{{*/
void forceupdate(Sheet *sheet)
{
int i;
assert(sheet!=(Sheet*)0);
for (i=0; i<sheet->dimx*sheet->dimy*sheet->dimz; ++i) if (*(sheet->sheet+i) != NULLCELL)
{
(*(sheet->sheet+i))->updated=0;
(*(sheet->sheet+i))->clock_t0=0;
(*(sheet->sheet+i))->clock_t1=0;
(*(sheet->sheet+i))->clock_t2=0;
}
update(sheet);
}
/*}}}*/
/* freecell -- free one cell */ /*{{{*/
void freecell(Sheet *sheet, int x, int y, int z)
{
Cell *c;
assert(sheet!=(Sheet*)0);
if (sheet->sheet!=(Cell**)0 && CELL_IS_GOOD(sheet,x,y,z))
{
c = CELL_AT(sheet,x,y,z);
tvecfree(c->contents);
tvecfree(c->ccontents);
tfree(&(c->value));
tfree(&(c->resvalue));
free(c);
CELL_AT(sheet,x,y,z) = NULLCELL;
sheet->changed=1;
}
}
/*}}}*/
/* columnwidth -- get width of column */ /*{{{*/
int columnwidth(Sheet *sheet, int x, int z)
{
assert(sheet!=(Sheet*)0);
if (x<sheet->dimx && z<sheet->dimz) return (*(sheet->column+x*sheet->dimz+z));
else return DEF_COLUMNWIDTH;
}
/*}}}*/
/* setwidth -- set width of column */ /*{{{*/
void setwidth(Sheet *sheet, int x, int z, int width)
{
assert(sheet!=(Sheet*)0);
resize(sheet,x,1,z);
sheet->changed=1;
*(sheet->column+x*sheet->dimz+z)=width;
}
/*}}}*/
/* cellwidth -- get width of a cell */ /*{{{*/
int cellwidth(Sheet *sheet, int x, int y, int z)
{
int width;
if (SHADOWED(sheet,x,y,z)) return 0;
width=columnwidth(sheet,x,z);
for (++x; SHADOWED(sheet,x,y,z); width+=columnwidth(sheet,x,z),++x);
return width;
}
/*}}}*/
/* putcont -- assign new contents */ /*{{{*/
void putcont(Sheet *sheet, int x, int y, int z, Token **t, int c)
{
assert(sheet!=(Sheet*)0);
sheet->changed=1;
resize(sheet,x,y,z);
initcell(sheet,x,y,z);
if (c)
{
tvecfree(CELL_AT(sheet,x,y,z)->ccontents);
CELL_AT(sheet,x,y,z)->ccontents=t;
}
else
{
tvecfree(CELL_AT(sheet,x,y,z)->contents);
CELL_AT(sheet,x,y,z)->contents=t;
}
redraw_cell(sheet, x, y, z);
}
/*}}}*/
/* getcont -- get contents */ /*{{{*/
Token **getcont(Sheet *sheet, int x, int y, int z, int c)
{
if (!CELL_IS_GOOD(sheet,x,y,z)) return EMPTY_TVEC;
else if (c==2) return (CELL_AT(sheet,x,y,z)->clock_t0 && CELL_AT(sheet,x,y,z)->ccontents ? CELL_AT(sheet,x,y,z)->ccontents : CELL_AT(sheet,x,y,z)->contents);
else return (c ? CELL_AT(sheet,x,y,z)->ccontents : CELL_AT(sheet,x,y,z)->contents);
}
/*}}}*/
/* getvalue -- get tcopy()ed value */ /*{{{*/
Token getvalue(Sheet *sheet, int x, int y, int z)
{
/* variables */ /*{{{*/
Token result;
int orig_upd_clock;
/*}}}*/
assert(sheet!=(Sheet*)0);
if (x<0 || y<0 || z<0)
/* return error */ /*{{{*/
{
result.type=EEK;
result.u.err=mystrmalloc(_("Negative index"));
return result;
}
/*}}}*/
result.type = EMPTY;
/* Can always short-circuit an out-of-bounds or empty cell */
if (!CELL_IS_GOOD(sheet,x,y,z)) return result;
/* only short-circuit on empty contents of a good cell if we are NOT
depending on this call to update the current value
*/
if (!upd_clock && getcont(sheet,x,y,z,2) == EMPTY_TVEC) return result;
/* update value of this cell if needed and return it */ /*{{{*/
orig_upd_clock = upd_clock;
if (CELL_AT(sheet,x,y,z)->ignored)
{
/* variables */ /*{{{*/
Token oldvalue;
/*}}}*/
oldvalue=CELL_AT(sheet,x,y,z)->value;
CELL_AT(sheet,x,y,z)->updated=1;
CELL_AT(sheet,x,y,z)->value.type=EMPTY;
tfree(&oldvalue);
}
else if (CELL_AT(sheet,x,y,z)->updated==0)
{
/* variables */ /*{{{*/
Sheet *old_sheet;
int old_x,old_y,old_z,old_max_eval;
Token oldvalue;
/*}}}*/
old_sheet=upd_sheet;
old_x=upd_x;
old_y=upd_y;
old_z=upd_z;
old_max_eval=max_eval;
upd_sheet=sheet;
upd_x=x;
upd_y=y;
upd_z=z;
max_eval=MAX_EVALNEST;
if (CELL_AT(sheet,x,y,z)->clock_t1==0)
{
CELL_AT(sheet,x,y,z)->updated = 1;
oldvalue = CELL_AT(sheet,x,y,z)->value;
upd_clock = 0;
CELL_AT(sheet,x,y,z)->value = eval_safe(getcont(sheet, x, y, z, 2));
tfree(&oldvalue);
}
else if (upd_clock)
{
CELL_AT(sheet,x,y,z)->updated=1;
upd_clock=0;
oldvalue=CELL_AT(sheet,x,y,z)->resvalue;
CELL_AT(sheet,x,y,z)->resvalue = eval_safe(getcont(sheet,x,y,z,2));
tfree(&oldvalue);
}
upd_sheet=old_sheet;
upd_x=old_x;
upd_y=old_y;
upd_z=old_z;
max_eval=old_max_eval;
}
if (!orig_upd_clock) result=tcopy(CELL_AT(sheet,x,y,z)->value);
/*}}}*/
return result;
}
/*}}}*/
/* update -- update all cells that need it */ /*{{{*/
void update(Sheet *sheet)
{
int x,y,z,kp,iterating;
assert(sheet!=(Sheet*)0);
kp=0;
iterating=0;
do
{
sheet->clk=0;
if (iterating==1)
{
line_msg((const char*)0,_("Calculating running, press Escape to abort it"));
++iterating;
}
else if (iterating==0) ++iterating;
for (ALL_CELLS_IN_SHEET(sheet,x,y,z))
{
if (CELL_AT(sheet,x,y,z) && CELL_AT(sheet,x,y,z)->clock_t2)
{
CELL_AT(sheet,x,y,z)->updated=0;
CELL_AT(sheet,x,y,z)->clock_t0=1;
CELL_AT(sheet,x,y,z)->clock_t1=1;
CELL_AT(sheet,x,y,z)->clock_t2=0;
}
}
for (ALL_CELLS_IN_SHEET(sheet,x,y,z))
{
upd_clock=1;
getvalue(sheet,x,y,z);
}
for (ALL_CELLS_IN_SHEET(sheet,x,y,z))
{
if (CELL_AT(sheet,x,y,z) && CELL_AT(sheet,x,y,z)->clock_t1)
{
tfree(&(CELL_AT(sheet,x,y,z)->value));
CELL_AT(sheet,x,y,z)->value = CELL_AT(sheet,x,y,z)->resvalue;
CELL_AT(sheet,x,y,z)->resvalue.type = EMPTY;
CELL_AT(sheet,x,y,z)->clock_t1 = 0;
}
}
upd_clock=0;
} while (sheet->clk && !(kp=keypressed()));
if (iterating==2) line_msg((const char*)0,kp ? _("Calculation aborted") : _("Calculation finished"));
sheet->clk=0;
redraw_sheet(sheet);
}
/*}}}*/
/* geterror -- get malloc()ed error string */ /*{{{*/
char *geterror(Sheet *sheet, int x, int y, int z)
{
Token v;
assert(sheet!=(Sheet*)0);
if ((v=getvalue(sheet,x,y,z)).type!=EEK)
{
tfree(&v);
return (char*)0;
}
else
{
return (v.u.err);
}
}
/*}}}*/
/* printvalue -- get ASCII representation of value */ /*{{{*/
void printvalue(char *s, size_t size, size_t chars, int quote, int scientific, int precision, Sheet *sheet, int x, int y, int z)
{
Token t;
assert(sheet!=(Sheet*)0);
t=getvalue(sheet,x,y,z);
printtok(s,size,chars,quote,scientific,precision,0,&t);
tfree(&t);
}
/*}}}*/
/* getadjust -- get cell adjustment */ /*{{{*/
Adjust getadjust(Sheet *sheet, int x, int y, int z)
{
assert(sheet!=(Sheet*)0);
if (!CELL_IS_GOOD(sheet,x,y,z))
{
return LEFT;
}
else if (CELL_AT(sheet,x,y,z)->adjust==AUTOADJUST) return (CELL_AT(sheet,x,y,z)->value.type==INT || CELL_AT(sheet,x,y,z)->value.type==FLOAT ? RIGHT : LEFT);
else return (CELL_AT(sheet,x,y,z)->adjust);
}
/*}}}*/
/* setadjust -- set cell adjustment */ /*{{{*/
void setadjust(Sheet *sheet, int x, int y, int z, Adjust adjust)
{
assert(sheet!=(Sheet*)0);
sheet->changed=1;
resize(sheet,x,y,z);
initcell(sheet,x,y,z);
CELL_AT(sheet,x,y,z)->adjust=adjust;
}
/*}}}*/
/* shadow -- shadow cell by left neighbour */ /*{{{*/
void shadow(Sheet *sheet, int x, int y, int z, int yep)
{
sheet->changed=1;
initcell(sheet,x,y,z);
CELL_AT(sheet,x,y,z)->shadowed=yep;
}
/*}}}*/
/* shadowed -- is cell shadowed? */ /*{{{*/
int shadowed(Sheet *sheet, int x, int y, int z)
{
return (SHADOWED(sheet,x,y,z));
}
/*}}}*/
/* bold -- bold font */ /*{{{*/
void bold(Sheet *sheet, int x, int y, int z, int yep)
{
sheet->changed=1;
initcell(sheet,x,y,z);
CELL_AT(sheet,x,y,z)->bold=yep;
}
/*}}}*/
/* isbold -- is cell bold? */ /*{{{*/
int isbold(Sheet *sheet, int x, int y, int z)
{
return (CELL_IS_GOOD(sheet,x,y,z) && CELL_AT(sheet,x,y,z)->bold);
}
/*}}}*/
/* underline -- underline */ /*{{{*/
void underline(Sheet *sheet, int x, int y, int z, int yep)
{
sheet->changed=1;
initcell(sheet,x,y,z);
CELL_AT(sheet,x,y,z)->underline=yep;
}
/*}}}*/
/* isunderline -- is cell underlined? */ /*{{{*/
int underlined(Sheet *sheet, int x, int y, int z)
{
return (CELL_IS_GOOD(sheet,x,y,z) && CELL_AT(sheet,x,y,z)->underline);
}
/*}}}*/
/* lockcell -- lock cell */ /*{{{*/
void lockcell(Sheet *sheet, int x, int y, int z, int yep)
{
sheet->changed=1;
initcell(sheet,x,y,z);
CELL_AT(sheet,x,y,z)->locked=yep;
}
/*}}}*/
/* locked -- is cell locked? */ /*{{{*/
int locked(Sheet *sheet, int x, int y, int z)
{
return (CELL_IS_GOOD(sheet,x,y,z) && CELL_AT(sheet,x,y,z)->locked);
}
/*}}}*/
/* transparent -- is cell transparent? */ /*{{{*/
int transparent(Sheet *sheet, int x, int y, int z)
{
return (CELL_IS_GOOD(sheet,x,y,z) && CELL_AT(sheet,x,y,z)->transparent);
}
/*}}}*/
/* maketrans -- make cell transparent */ /*{{{*/
void maketrans(Sheet *sheet, int x, int y, int z, int yep)
{
sheet->changed=1;
initcell(sheet,x,y,z);
CELL_AT(sheet,x,y,z)->transparent=yep;
}
/*}}}*/
/* igncell -- ignore cell */ /*{{{*/
void igncell(Sheet *sheet, int x, int y, int z, int yep)
{
sheet->changed=1;
initcell(sheet,x,y,z);
CELL_AT(sheet,x,y,z)->ignored=yep;
}
/*}}}*/
/* ignored -- is cell ignored? */ /*{{{*/
int ignored(Sheet *sheet, int x, int y, int z)
{
return (CELL_IS_GOOD(sheet,x,y,z) && CELL_AT(sheet,x,y,z)->ignored);
}
/*}}}*/
/* clk -- clock cell */ /*{{{*/
void clk(Sheet *sheet, int x, int y, int z)
{
assert(sheet!=(Sheet*)0);
assert(x>=0 && x<sheet->dimx);
assert(y>=0 && y<sheet->dimy);
assert(z>=0 && z<sheet->dimz);
if (CELL_AT(sheet,x,y,z))
{
CELL_AT(sheet,x,y,z)->clock_t2=1;
sheet->clk=1;
}
}
/*}}}*/
/* setscientific -- cell value should be displayed in scientific notation */ /*{{{*/
void setscientific(Sheet *sheet, int x, int y, int z, int yep)
{
sheet->changed=1;
resize(sheet,x,y,z);
initcell(sheet,x,y,z);
CELL_AT(sheet,x,y,z)->scientific=yep;
}
/*}}}*/
/* getscientific -- should value be displayed in scientific notation? */ /*{{{*/
int getscientific(Sheet *sheet, int x, int y, int z)
{
if (CELL_IS_GOOD(sheet,x,y,z)) return CELL_AT(sheet,x,y,z)->scientific;
else return DEF_SCIENTIFIC;
}
/*}}}*/
/* setprecision -- set cell precision */ /*{{{*/
void setprecision(Sheet *sheet, int x, int y, int z, int precision)
{
sheet->changed=1;
resize(sheet,x,y,z);
initcell(sheet,x,y,z);
CELL_AT(sheet,x,y,z)->precision=precision;
}
/*}}}*/
/* getprecision -- get cell precision */ /*{{{*/
int getprecision(Sheet *sheet, int x, int y, int z)
{
if (CELL_IS_GOOD(sheet,x,y,z)) return (CELL_AT(sheet,x,y,z)->precision==-1 ? def_precision : CELL_AT(sheet,x,y,z)->precision);
else return def_precision;
}
/*}}}*/
/* getlabel -- get cell label */ /*{{{*/
const char *getlabel(Sheet *sheet, int x, int y, int z)
{
if (!CELL_IS_GOOD(sheet,x,y,z) || CELL_AT(sheet,x,y,z)->label==(char*)0) return "";
else return (CELL_AT(sheet,x,y,z)->label);
}
/*}}}*/
/* setlabel -- set cell label */ /*{{{*/
void setlabel(Sheet *sheet, int x, int y, int z, const char *buf, int update)
{
sheet->changed=1;
resize(sheet,x,y,z);
initcell(sheet,x,y,z);
if (CELL_AT(sheet,x,y,z)->label!=(char*)0) free(CELL_AT(sheet,x,y,z)->label);
if (*buf!='\0') CELL_AT(sheet,x,y,z)->label=strcpy(malloc(strlen(buf)+1),buf);
else CELL_AT(sheet,x,y,z)->label=(char*)0;
if (update)
{
cachelabels(sheet);
forceupdate(sheet);
}
}
/*}}}*/
/* findlabel -- return cell location for a given label */ /*{{{*/
Token findlabel(Sheet *sheet, const char *label)
{
/* variables */ /*{{{*/
Token result;
unsigned long hx;
struct Label *run;
/*}}}*/
assert(sheet!=(Sheet*)0);
/*
if (sheet==(Sheet*)0) run=(struct Label*)0;
else
*/
{
HASH(hx,label);
for (run=sheet->labelcache[(unsigned int)hx]; run!=(struct Label*)0 && strcmp(label,run->label); run=run->next);
}
if (run)
{
result.type=LOCATION;
result.u.location[0]=run->x;
result.u.location[1]=run->y;
result.u.location[2]=run->z;
}
else
{
result.type=EEK;
result.u.err=mystrmalloc(_("No such label"));
}
return result;
}
/*}}}*/
/* relabel -- search and replace for labels */ /*{{{*/
void relabel(Sheet *sheet, const char *oldlabel, const char *newlabel, int x, int y, int z)
{
/* variables */ /*{{{*/
Token **run;
/*}}}*/
/* asserts */ /*{{{*/
assert(sheet!=(Sheet*)0);
assert(oldlabel!=(const char*)0);
assert(newlabel!=(const char*)0);
assert(x>=0);
assert(y>=0);
assert(z>=0);
/*}}}*/
if (CELL_IS_GOOD(sheet,x,y,z))
{
if (CELL_AT(sheet,x,y,z)->contents != EMPTY_TVEC)
{
for (run=CELL_AT(sheet,x,y,z)->contents; *run!=NULLTOKEN; ++run)
{
if ((*run)->type==LIDENT && strcmp((*run)->u.lident,oldlabel)==0)
{
free((*run)->u.lident);
(*run)->u.lident=mystrmalloc(newlabel);
}
}
}
if (CELL_AT(sheet,x,y,z)->ccontents != EMPTY_TVEC)
{
for (run=CELL_AT(sheet,x,y,z)->ccontents; *run!=NULLTOKEN; ++run)
{
if ((*run)->type==LIDENT && strcmp((*run)->u.lident,oldlabel)==0)
{
free((*run)->u.lident);
(*run)->u.lident=mystrmalloc(newlabel);
}
}
}
}
cachelabels(sheet);
forceupdate(sheet);
}
/*}}}*/
/* savexdr -- save a spread sheet in XDR */ /*{{{*/
const char *savexdr(Sheet *sheet, const char *name, unsigned int *count)
{
/* variables */ /*{{{*/
FILE *fp;
XDR xdrs;
int x,y,z;
/*}}}*/
*count=0;
if ((fp=fopen(name,"w"))==(FILE*)0) return strerror(errno);
xdrstdio_create(&xdrs,fp,XDR_ENCODE);
if (!xdr_magic(&xdrs))
{
xdr_destroy(&xdrs);
(void)fclose(fp);
return strerror(errno);
}
for (x=sheet->dimx-1; x>=0; --x) for (z=sheet->dimz-1; z>=0; --z)
{
int width;
int u;
width=columnwidth(sheet,x,z);
if (width!=DEF_COLUMNWIDTH)
{
u=0;
if (xdr_int(&xdrs,&u)==0 || xdr_column(&xdrs,&x,&z,&width)==0)
{
xdr_destroy(&xdrs);
(void)fclose(fp);
return strerror(errno);
}
}
for (y=sheet->dimy-1; y>=0; --y)
{
if (CELL_AT(sheet,x,y,z)!=NULLCELL)
{
u=1;
if (xdr_int(&xdrs,&u)==0 || xdr_int(&xdrs,&x)==0 || xdr_int(&xdrs,&y)==0 || xdr_int(&xdrs,&z)==0 || xdr_cell(&xdrs,CELL_AT(sheet,x,y,z))==0)
{
xdr_destroy(&xdrs);
(void)fclose(fp);
return strerror(errno);
}
++*count;
}
}
}
xdr_destroy(&xdrs);
if (fclose(fp)==EOF) return strerror(errno);
sheet->changed=0;
return (const char*)0;
}
/*}}}*/
/* savetbl -- save as tbl tyble */ /*{{{*/
const char *savetbl(Sheet *sheet, const char *name, int body, int x1, int y1, int z1, int x2, int y2, int z2, unsigned int *count)
{
/* variables */ /*{{{*/
FILE *fp=(FILE*)0; /* cause run time error */
int x,y,z;
char buf[1024];
char num[20];
char fullname[PATH_MAX];
/*}}}*/
/* asserts */ /*{{{*/
assert(sheet!=(Sheet*)0);
assert(name!=(const char*)0);
/*}}}*/
*count=0;
for (z=z1; z<=z2; ++z) for (y=y1; y<=y2; ++y) if (shadowed(sheet,x1,y,z)) return _("Shadowed cells in first column");
if (!body && (fp=fopen(name,"w"))==(FILE*)0) return strerror(errno);
for (z=z1; z<=z2; ++z)
{
if (body)
/* open new file */ /*{{{*/
{
sprintf(num,".%d",z);
fullname[sizeof(fullname)-strlen(num)-1]='\0';
(void)strncpy(fullname,name,sizeof(fullname)-strlen(num)-1);
fullname[sizeof(fullname)-1]='\0';
(void)strncat(fullname,num,sizeof(fullname)-strlen(num)-1);
fullname[sizeof(fullname)-1]='\0';
if ((fp=fopen(fullname,"w"))==(FILE*)0) return strerror(errno);
}
/*}}}*/
else if (fputs_close(".TS\n",fp)==EOF) return strerror(errno);
for (y=y1; y<=y2; ++y)
{
/* print format */ /*{{{*/
if (y>y1 && fputs_close(".T&\n",fp)==EOF) return strerror(errno);
for (x=x1; x<=x2; ++x)
{
if (x>x1 && fputc_close(' ',fp)==EOF) return strerror(errno);
if (shadowed(sheet,x,y,z))
{
if (fputc_close('s',fp)==EOF) return strerror(errno);
}
if (isbold(sheet,x,y,z))
{
if (fputc_close('b',fp)==EOF) return strerror(errno);
}
if (underlined(sheet,x,y,z))
{
if (fputc_close('u',fp)==EOF) return strerror(errno);
}
else switch (getadjust(sheet,x,y,z))
{
case LEFT: if (fputc_close('l',fp)==EOF) return strerror(errno); break;
case RIGHT: if (fputc_close('r',fp)==EOF) return strerror(errno); break;
case CENTER: if (fputc_close('c',fp)==EOF) return strerror(errno); break;
default: assert(0);
}
}
if (fputs_close(".\n",fp)==EOF) return strerror(errno);
/*}}}*/
/* print contents */ /*{{{*/
for (x=x1; x<=x2; ++x)
{
if (!shadowed(sheet,x,y,z))
{
if (x>x1 && fputc_close('\t',fp)==EOF) return strerror(errno);
if (CELL_AT(sheet,x,y,z)!=NULLCELL)
{
char *bufp;
printvalue(buf,sizeof(buf),0,0,getscientific(sheet,x,y,z),getprecision(sheet,x,y,z),sheet,x,y,z);
if (transparent(sheet,x,y,z))
{
if (fputs_close(buf,fp)==EOF) return strerror(errno);
}
else for (bufp=buf; *bufp; ++bufp) switch (*bufp)
{
case '\\':
{
if (fputc_close('\\',fp)==EOF || fputc_close('e',fp)==EOF) return strerror(errno);
break;
}
case '_':
{
if (fputc_close('\\',fp)==EOF || fputc_close('&',fp)==EOF || fputc_close('_',fp)==EOF) return strerror(errno);
break;
}
case '.':
{
if (x==x1 && bufp==buf && (fputc_close('\\',fp)==EOF || fputc_close('&',fp)==EOF)) return strerror(errno);
if (fputc_close('.',fp)==EOF) return strerror(errno);
break;
}
case '\'':
{
if (x==x1 && bufp==buf && (fputc_close('\\',fp)==EOF || fputc_close('&',fp)==EOF)) return strerror(errno);
if (fputc_close('\'',fp)==EOF) return strerror(errno);
break;
}
case '-':
{
if (*(bufp+1)=='-')
{
if (fputc_close('-',fp)==EOF) return strerror(errno);
else ++bufp;
}
else if (fputs_close("\\-",fp)==EOF) return strerror(errno);
break;
}
default: if (fputc_close(*bufp,fp)==EOF) return strerror(errno);
}
}
}
}
if (fputc_close('\n',fp)==EOF) return strerror(errno);
/*}}}*/
++*count;
}
if (!body)
{
if (fputs_close(".TE\n",fp)==EOF) return strerror(errno);
if (z<z2 && fputs_close(".bp\n",fp)==EOF) return strerror(errno);
}
if (body && fclose(fp)==EOF) return strerror(errno);
}
if (!body && fclose(fp)==EOF) return strerror(errno);
return (const char*)0;
}
/*}}}*/
/* savetext -- save as text */ /*{{{*/
const char *savetext(Sheet *sheet, const char *name, int x1, int y1, int z1, int x2, int y2, int z2, unsigned int *count)
{
/* variables */ /*{{{*/
FILE *fp;
int x,y,z;
/*}}}*/
/* asserts */ /*{{{*/
assert(sheet!=(Sheet*)0);
assert(name!=(const char*)0);
/*}}}*/
*count=0;
for (z=z1; z<=z2; ++z) for (y=y1; y<=y2; ++y) if (shadowed(sheet,x1,y,z)) return _("Shadowed cells in first column");
if ((fp=fopen(name,"w"))==(FILE*)0) return strerror(errno);
for (z=z1; z<=z2; ++z)
{
for (y=y1; y<=y2; ++y)
{
size_t size,fill;
for (x=x1; x<=x2; ++x)
{
size=cellwidth(sheet,x,y,z);
if (CELL_AT(sheet,x,y,z)!=NULLCELL)
{
char *buf;
buf=malloc(size*UTF8SZ+1);
printvalue(buf,size*UTF8SZ+1,size,0,getscientific(sheet,x,y,z),getprecision(sheet,x,y,z),sheet,x,y,z);
adjust(getadjust(sheet,x,y,z),buf,size);
if (fputs_close(buf,fp)==EOF)
{
free(buf);
return strerror(errno);
}
for (fill=strlen(buf); fill<size; ++fill) if (fputc_close(' ',fp)==EOF)
{
free(buf);
return strerror(errno);
}
free(buf);
}
else
{
for (fill=0; fill<size; ++fill) if (fputc_close(' ',fp)==EOF) return strerror(errno);
}
++*count;
}
if (fputc_close('\n',fp)==EOF) return strerror(errno);
}
if (z<z2 && fputs_close("\f",fp)==EOF) return strerror(errno);
}
if (fclose(fp)==EOF) return strerror(errno);
return (const char*)0;
}
/*}}}*/
/* savecsv -- save as CSV */ /*{{{*/
const char *savecsv(Sheet *sheet, const char *name, char sep, int x1, int y1, int z1, int x2, int y2, int z2, unsigned int *count)
{
/* variables */ /*{{{*/
FILE *fp;
int x,y,z;
/*}}}*/
/* asserts */ /*{{{*/
assert(sheet!=(Sheet*)0);
assert(name!=(const char*)0);
/*}}}*/
*count=0;
for (z=z1; z<=z2; ++z) for (y=y1; y<=y2; ++y) if (shadowed(sheet,x1,y,z)) return _("Shadowed cells in first column");
if ((fp=fopen(name,"w"))==(FILE*)0) return strerror(errno);
for (z=z1; z<=z2; ++z)
{
for (y=y1; y<=y2; ++y)
{
for (x=x1; x<=x2; ++x)
{
if (x>x1) if (fputc_close(sep,fp)==EOF) return strerror(errno);
if (CELL_AT(sheet,x,y,z)!=NULLCELL)
{
char *buf,*s;
buf=malloc(255*UTF8SZ+1);
printvalue(buf,255*UTF8SZ+1,255,0,getscientific(sheet,x,y,z),getprecision(sheet,x,y,z),sheet,x,y,z);
if (CELL_AT(sheet,x,y,z)->value.type==STRING && fputc_close('"',fp)==EOF)
{
free(buf);
return strerror(errno);
}
for (s=buf; *s; ++s)
{
if (fputc_close(*s,fp)==EOF || (*s=='"' && fputc_close(*s,fp)==EOF))
{
free(buf);
return strerror(errno);
}
}
free(buf);
if (CELL_AT(sheet,x,y,z)->value.type==STRING && fputc_close('"',fp)==EOF) return strerror(errno);
}
++*count;
}
if (fputc_close('\n',fp)==EOF) return strerror(errno);
}
if (z<z2 && fputs_close("\f",fp)==EOF) return strerror(errno);
}
if (fclose(fp)==EOF) return strerror(errno);
return (const char*)0;
}
/*}}}*/
/* saveport -- save as portable text */ /*{{{*/
const char *saveport(Sheet *sheet, const char *name, unsigned int *count)
{
/* variables */ /*{{{*/
FILE *fp;
int x,y,z;
/*}}}*/
/* asserts */ /*{{{*/
assert(sheet!=(Sheet*)0);
assert(name!=(const char*)0);
/*}}}*/
*count=0;
if ((fp=fopen(name,"w"))==(FILE*)0) return strerror(errno);
fprintf(fp,"# This is a work sheet generated with teapot %s.\n",VERSION);
for (z=sheet->dimz-1; z>=0; --z)
{
for (y=sheet->dimy-1; y>=0; --y)
{
for (x=sheet->dimx-1; x>=0; --x)
{
if (y==0) if (columnwidth(sheet,x,z)!=DEF_COLUMNWIDTH) fprintf(fp,"W%d %d %d\n",x,z,columnwidth(sheet,x,z));
if (CELL_AT(sheet,x,y,z)!=NULLCELL)
{
fprintf(fp,"C%d %d %d ",x,y,z);
if (CELL_AT(sheet,x,y,z)->adjust!=AUTOADJUST) fprintf(fp,"A%c ","lrc"[CELL_AT(sheet,x,y,z)->adjust]);
if (CELL_AT(sheet,x,y,z)->label) fprintf(fp,"L%s ",CELL_AT(sheet,x,y,z)->label);
if (CELL_AT(sheet,x,y,z)->precision!=-1) fprintf(fp,"P%d ",CELL_AT(sheet,x,y,z)->precision);
if (CELL_AT(sheet,x,y,z)->shadowed) fprintf(fp,"S ");
if (CELL_AT(sheet,x,y,z)->bold) fprintf(fp,"B ");
if (CELL_AT(sheet,x,y,z)->underline) fprintf(fp,"U ");
if (CELL_AT(sheet,x,y,z)->scientific!=DEF_SCIENTIFIC) fprintf(fp,"E ");
if (CELL_AT(sheet,x,y,z)->locked) fprintf(fp,"C ");
if (CELL_AT(sheet,x,y,z)->transparent) fprintf(fp,"T ");
if (CELL_AT(sheet,x,y,z)->contents)
{
char buf[4096];
if (fputc_close(':',fp)==EOF) return strerror(errno);
print(buf,sizeof(buf),0,1,CELL_AT(sheet,x,y,z)->scientific,CELL_AT(sheet,x,y,z)->precision,CELL_AT(sheet,x,y,z)->contents);
if (fputs_close(buf,fp)==EOF) return strerror(errno);
}
if (CELL_AT(sheet,x,y,z)->ccontents)
{
char buf[4096];
if (fputs_close("\\\n",fp)==EOF) return strerror(errno);
print(buf,sizeof(buf),0,1,CELL_AT(sheet,x,y,z)->scientific,CELL_AT(sheet,x,y,z)->precision,CELL_AT(sheet,x,y,z)->ccontents);
if (fputs_close(buf,fp)==EOF) return strerror(errno);
}
if (fputc_close('\n',fp)==EOF) return strerror(errno);
++*count;
}
}
}
}
if (fclose(fp)==EOF) return strerror(errno);
return (const char*)0;
}
/*}}}*/
/* loadport -- load from portable text */ /*{{{*/
const char *loadport(Sheet *sheet, const char *name)
{
/* variables */ /*{{{*/
static char errbuf[80];
FILE *fp;
int x,y,z;
char buf[4096];
int line;
const char *ns,*os;
const char *err;
int precision;
char *label;
Adjust adjust;
int shadowed;
int bold;
int underline;
int scientific;
int locked;
int transparent;
int ignored;
Token **contents,**ccontents;
int width;
/*}}}*/
if ((fp=fopen(name,"r"))==(FILE*)0) return strerror(errno);
freesheet(sheet,0);
err=(const char*)0;
line=1;
while (fgets(buf,sizeof(buf),fp)!=(char*)0)
{
/* remove nl */ /*{{{*/
width=strlen(buf);
if (width>0 && buf[width-1]=='\n') buf[--width]='\0';
/*}}}*/
switch (buf[0])
{
/* C -- parse cell */ /*{{{*/
case 'C':
{
int cc=0;
if (width>0 && buf[width-1]=='\\') { buf[--width]='\0'; cc=1; }
adjust=AUTOADJUST;
precision=-1;
label=(char*)0;
contents=(Token**)0;
ccontents=(Token**)0;
shadowed=0;
bold=0;
underline=0;
scientific=DEF_SCIENTIFIC;
locked=0;
transparent=0;
ignored=0;
/* parse x y and z */ /*{{{*/
os=ns=buf+1;
x=posnumber(os,&ns);
if (os==ns)
{
sprintf(errbuf,_("Parse error for x position in line %d"),line);
err=errbuf;
goto eek;
}
while (*ns==' ') ++ns;
os=ns;
y=posnumber(os,&ns);
if (os==ns)
{
sprintf(errbuf,_("Parse error for y position in line %d"),line);
err=errbuf;
goto eek;
}
while (*ns==' ') ++ns;
os=ns;
z=posnumber(os,&ns);
if (os==ns)
{
sprintf(errbuf,_("Parse error for z position in line %d"),line);
err=errbuf;
goto eek;
}
/*}}}*/
/* parse optional attributes */ /*{{{*/
do
{
while (*ns==' ') ++ns;
switch (*ns)
{
/* A -- adjustment */ /*{{{*/
case 'A':
{
++ns;
switch (*ns)
{
case 'l': adjust=LEFT; ++ns; break;
case 'r': adjust=RIGHT; ++ns; break;
case 'c': adjust=CENTER; ++ns; break;
default: sprintf(errbuf,_("Parse error for adjustment in line %d"),line); err=errbuf; goto eek;
}
break;
}
/*}}}*/
/* L -- label */ /*{{{*/
case 'L':
{
char buf[1024],*p;
p=buf;
++ns;
while (*ns && *ns!=' ') { *p=*ns; ++p; ++ns; }
*p='\0';
label=mystrmalloc(buf);
break;
}
/*}}}*/
/* P -- precision */ /*{{{*/
case 'P':
{
os=++ns;
precision=posnumber(os,&ns);
if (os==ns)
{
sprintf(errbuf,_("Parse error for precision in line %d"),line);
err=errbuf;
goto eek;
}
break;
}
/*}}}*/
/* S -- shadowed */ /*{{{*/
case 'S':
{
if (x==0)
{
sprintf(errbuf,_("Trying to shadow cell (%d,%d,%d) in line %d"),x,y,z,line);
err=errbuf;
goto eek;
}
++ns;
shadowed=1;
break;
}
/*}}}*/
/* U -- underline */ /*{{{*/
case 'U':
{
if (x==0)
{
sprintf(errbuf,_("Trying to underline cell (%d,%d,%d) in line %d"),x,y,z,line);
err=errbuf;
goto eek;
}
++ns;
underline=1;
break;
}
/*}}}*/
/* B -- bold */ /*{{{*/
case 'B':
{
if (x==0)
{
sprintf(errbuf,_("Trying to bold cell (%d,%d,%d) in line %d"),x,y,z,line);
err=errbuf;
goto eek;
}
++ns;
bold=1;
break;
}
/*}}}*/
/* E -- scientific */ /*{{{*/
case 'E':
{
++ns;
scientific=1;
break;
}
/*}}}*/
/* O -- locked */ /*{{{*/
case 'O':
{
++ns;
locked=1;
break;
}
/*}}}*/
/* T -- transparent */ /*{{{*/
case 'T':
{
++ns;
transparent=1;
break;
}
/*}}}*/
/* I -- ignored */ /*{{{*/
case 'I':
{
++ns;
ignored=1;
break;
}
/*}}}*/
/* : \0 -- do nothing */ /*{{{*/
case ':':
case '\0': break;
/*}}}*/
/* default -- error */ /*{{{*/
default: sprintf(errbuf,_("Invalid option %c in line %d"),*ns,line); err=errbuf; goto eek;
/*}}}*/
}
} while (*ns!=':' && *ns!='\0');
/*}}}*/
/* convert remaining string into token sequence */ /*{{{*/
if (*ns)
{
++ns;
contents=scan(&ns);
if (contents==(Token**)0)
{
tvecfree(contents);
sprintf(errbuf,_("Expression syntax error in line %d"),line);
err=errbuf;
goto eek;
}
}
/*}}}*/
/* convert remaining string into token sequence */ /*{{{*/
if (cc && fgets(buf,sizeof(buf),fp)!=(char*)0)
{
++line;
/* remove nl */ /*{{{*/
width=strlen(buf);
if (width>0 && buf[width-1]=='\n') buf[width-1]='\0';
/*}}}*/
ns=buf;
ccontents=scan(&ns);
if (ccontents==(Token**)0)
{
tvecfree(ccontents);
sprintf(errbuf,_("Expression syntax error in line %d"),line);
err=errbuf;
goto eek;
}
}
/*}}}*/
initcell(sheet,x,y,z);
CELL_AT(sheet,x,y,z)->adjust=adjust;
CELL_AT(sheet,x,y,z)->label=label;
CELL_AT(sheet,x,y,z)->precision=precision;
CELL_AT(sheet,x,y,z)->shadowed=shadowed;
CELL_AT(sheet,x,y,z)->bold=bold;
CELL_AT(sheet,x,y,z)->underline=underline;
CELL_AT(sheet,x,y,z)->scientific=scientific;
CELL_AT(sheet,x,y,z)->locked=locked;
CELL_AT(sheet,x,y,z)->transparent=transparent;
CELL_AT(sheet,x,y,z)->ignored=ignored;
CELL_AT(sheet,x,y,z)->contents=contents;
CELL_AT(sheet,x,y,z)->ccontents=ccontents;
break;
}
/*}}}*/
/* W -- column width */ /*{{{*/
case 'W':
{
/* parse x and z */ /*{{{*/
os=ns=buf+1;
x=posnumber(os,&ns);
if (os==ns)
{
sprintf(errbuf,_("Parse error for x position in line %d"),line);
err=errbuf;
goto eek;
}
while (*ns==' ') ++ns;
os=ns;
z=posnumber(os,&ns);
if (os==ns)
{
sprintf(errbuf,_("Parse error for z position in line %d"),line);
err=errbuf;
goto eek;
}
/*}}}*/
/* parse width */ /*{{{*/
while (*ns==' ') ++ns;
os=ns;
width=posnumber(os,&ns);
if (os==ns)
{
sprintf(errbuf,_("Parse error for width in line %d"),line);
err=errbuf;
goto eek;
}
/*}}}*/
setwidth(sheet,x,z,width);
break;
}
/*}}}*/
/* # -- comment */ /*{{{*/
case '#': break;
/*}}}*/
/* default -- error */ /*{{{*/
default:
{
sprintf(errbuf,_("Unknown tag %c in line %d"),buf[0],line);
err=errbuf;
goto eek;
}
/*}}}*/
}
++line;
}
eek:
if (fclose(fp)==EOF && err==(const char*)0) err=strerror(errno);
sheet->changed=0;
cachelabels(sheet);
forceupdate(sheet);
return err;
}
/*}}}*/
/* loadxdr -- load a spread sheet in XDR */ /*{{{*/
const char *loadxdr(Sheet *sheet, const char *name)
{
/* variables */ /*{{{*/
FILE *fp;
XDR xdrs;
int x,y,z;
int width;
int u;
int olderror;
/*}}}*/
if ((fp=fopen(name,"r"))==(FILE*)0) return strerror(errno);
xdrstdio_create(&xdrs,fp,XDR_DECODE);
if (!xdr_magic(&xdrs))
{
#if 0
xdr_destroy(&xdrs);
fclose(fp);
return _("This is not a teapot worksheet in XDR format");
#else
xdr_destroy(&xdrs);
rewind(fp);
xdrstdio_create(&xdrs,fp,XDR_DECODE);
#endif
}
freesheet(sheet,0);
while (xdr_int(&xdrs,&u)) switch (u)
{
/* 0 -- column width element */ /*{{{*/
case 0:
{
if (xdr_column(&xdrs,&x,&z,&width)==0)
{
olderror=errno;
xdr_destroy(&xdrs);
(void)fclose(fp);
return strerror(olderror);
}
setwidth(sheet,x,z,width);
break;
}
/*}}}*/
/* 1 -- cell element */ /*{{{*/
case 1:
{
if (xdr_int(&xdrs,&x)==0 || xdr_int(&xdrs,&y)==0 || xdr_int(&xdrs,&z)==0)
{
olderror=errno;
xdr_destroy(&xdrs);
(void)fclose(fp);
return strerror(olderror);
}
initcell(sheet,x,y,z);
if (xdr_cell(&xdrs,CELL_AT(sheet,x,y,z))==0)
{
freecell(sheet,x,y,z);
olderror=errno;
xdr_destroy(&xdrs);
(void)fclose(fp);
return strerror(olderror);
}
break;
}
/*}}}*/
/* default -- should not happen */ /*{{{*/
default:
{
xdr_destroy(&xdrs);
fclose(fp);
sheet->changed=0;
cachelabels(sheet);
forceupdate(sheet);
return _("Invalid record, loading aborted");
}
/*}}}*/
}
xdr_destroy(&xdrs);
if (fclose(fp)==EOF) return strerror(errno);
sheet->changed=0;
cachelabels(sheet);
forceupdate(sheet);
redraw_sheet(sheet);
return (const char*)0;
}
/*}}}*/
/* loadcsv -- load/merge CSVs */ /*{{{*/
const char *loadcsv(Sheet *sheet, const char *name)
{
/* variables */ /*{{{*/
FILE *fp;
Token **t;
const char *err;
int line,x;
char ln[4096];
const char *str;
double value;
long lvalue;
int separator = 0;
/*}}}*/
if ((fp=fopen(name,"r"))==(FILE*)0) return strerror(errno);
err=(const char*)0;
for (x=0,line=1; fgets(ln,sizeof(ln),fp); ++line)
{
const char *s;
const char *cend;
if (!separator) { /* FIXME: find a better way to autodetect */
int ccnt = 0, scnt = 0;
char *pos = ln;
while ((pos = strchr(pos, ','))) pos++, ccnt++;
pos = ln;
while ((pos = strchr(pos, ';'))) pos++, scnt++;
if (ccnt || scnt) separator = 1;
csv_setopt(scnt > ccnt);
}
s=cend=ln;
x=0;
do
{
t=malloc(2*sizeof(Token*));
t[0]=malloc(sizeof(Token));
t[1]=(Token*)0;
lvalue=csv_long(s,&cend);
if (s!=cend) /* ok, it is a integer */ /*{{{*/
{
t[0]->type=INT;
t[0]->u.integer=lvalue;
putcont(sheet, sheet->curx+x, sheet->cury+line-1, sheet->curz, t, 0);
}
/*}}}*/
else
{
value=csv_double(s,&cend);
if (s!=cend) /* ok, it is a double */ /*{{{*/
{
t[0]->type=FLOAT;
t[0]->u.flt=value;
putcont(sheet, sheet->curx+x, sheet->cury+line-1, sheet->curz, t, 0);
}
/*}}}*/
else
{
str=csv_string(s,&cend);
if (s!=cend) /* ok, it is a string */ /*{{{*/
{
t[0]->type=STRING;
t[0]->u.string=mystrmalloc(str);
putcont(sheet, sheet->curx+x, sheet->cury+line-1, sheet->curz, t, 0);
}
/*}}}*/
else
{
tvecfree(t);
csv_separator(s,&cend);
while (s==cend && *s && *s!='\n')
{
err=_("unknown values ignored");
csv_separator(++s,&cend);
}
/* else it is nothing, which does not need to be stored :) */
}
}
}
} while (s!=cend ? s=cend,++x,1 : 0);
}
fclose(fp);
return err;
}
/*}}}*/
/* insertcube -- insert a block */ /*{{{*/
void insertcube(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, Direction ins)
{
/* variables */ /*{{{*/
int x,y,z;
/*}}}*/
switch (ins)
{
/* IN_X */ /*{{{*/
case IN_X:
{
int right;
right=sheet->dimx+x2-x1;
for (z=z1; z<=z2; ++z) for (y=y1; y<=y2; ++y) for (x=right; x>x2; --x)
{
resize(sheet,x,y,z);
CELL_AT(sheet,x,y,z)=CELL_AT(sheet,x-(x2-x1+1),y,z);
CELL_AT(sheet,x-(x2-x1+1),y,z)=NULLCELL;
}
break;
}
/*}}}*/
/* IN_Y */ /*{{{*/
case IN_Y:
{
int down;
down=sheet->dimy+y2-y1;
for (z=z1; z<=z2; ++z) for (x=x1; x<=x2; ++x) for (y=down; y>y2; --y)
{
resize(sheet,x,y,z);
CELL_AT(sheet,x,y,z)=CELL_AT(sheet,x,y-(y2-y1+1),z);
CELL_AT(sheet,x,y-(y2-y1+1),z)=NULLCELL;
}
break;
}
/*}}}*/
/* IN_Z */ /*{{{*/
case IN_Z:
{
int bottom;
bottom=sheet->dimz+z2-z1;
for (y=y1; y<=y2; ++y) for (x=x1; x<=x2; ++x) for (z=bottom; z>z2; --z)
{
resize(sheet,x,y,z);
CELL_AT(sheet,x,y,z)=CELL_AT(sheet,x,y,z-(z2-z1+1));
CELL_AT(sheet,x,y,z-(z2-z1+1))=NULLCELL;
}
break;
}
/*}}}*/
/* default */ /*{{{*/
default: assert(0);
/*}}}*/
}
sheet->changed=1;
cachelabels(sheet);
forceupdate(sheet);
}
/*}}}*/
/* deletecube -- delete a block */ /*{{{*/
void deletecube(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, Direction del)
{
/* variables */ /*{{{*/
int x,y,z;
/*}}}*/
/* free cells in marked block */ /*{{{*/
for (x=x1; x<=x2; ++x)
for (y=y1; y<=y2; ++y)
for (z=z1; z<=z2; ++z)
freecell(sheet,x,y,z);
/*}}}*/
switch (del)
{
/* IN_X */ /*{{{*/
case IN_X:
{
for (z=z1; z<=z2; ++z) for (y=y1; y<=y2; ++y) for (x=x1; x<=sheet->dimx-(x2-x1+1); ++x)
{
if (x+(x2-x1+1)<sheet->dimx && y<sheet->dimy && z<sheet->dimz)
{
CELL_AT(sheet,x,y,z)=CELL_AT(sheet,x+(x2-x1+1),y,z);
CELL_AT(sheet,x+(x2-x1+1),y,z)=NULLCELL;
}
}
break;
}
/*}}}*/
/* IN_Y */ /*{{{*/
case IN_Y:
{
for (z=z1; z<=z2; ++z) for (x=x1; x<=x2; ++x) for (y=y1; y<=sheet->dimy-(y2-y1+1); ++y)
{
if (x<sheet->dimx && y+(y2-y1+1)<sheet->dimy && z<sheet->dimz)
{
CELL_AT(sheet,x,y,z)=CELL_AT(sheet,x,y+(y2-y1+1),z);
CELL_AT(sheet,x,y+(y2-y1+1),z)=NULLCELL;
}
}
break;
}
/*}}}*/
/* IN_Z */ /*{{{*/
case IN_Z:
{
for (y=y1; y<=y2; ++y) for (x=x1; x<=x2; ++x) for (z=z1; z<=sheet->dimz-(z2-z1+1); ++z)
{
if (x<sheet->dimx && y<sheet->dimy && z+(z2-z1+1)<sheet->dimz)
{
CELL_AT(sheet,x,y,z)=CELL_AT(sheet,x,y,z+(z2-z1+1));
CELL_AT(sheet,x,y,z+(z2-z1+1))=NULLCELL;
}
}
break;
}
/*}}}*/
/* default */ /*{{{*/
default: assert(0);
/*}}}*/
}
sheet->changed=1;
cachelabels(sheet);
forceupdate(sheet);
}
/*}}}*/
/* moveblock -- move a block */ /*{{{*/
void moveblock(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3, int copy)
{
/* variables */ /*{{{*/
int dirx, diry, dirz;
int widx, widy, widz;
int x, y, z;
int xf, xt, yf, yt, zf, zt;
/*}}}*/
if (x1==x3 && y1==y3 && z1==z3) return;
widx=(x2-x1);
widy=(y2-y1);
widz=(z2-z1);
if (x3>x1) { dirx=-1; xf=widx; xt=-1; } else { dirx=1; xf=0; xt=widx+1; }
if (y3>y1) { diry=-1; yf=widy; yt=-1; } else { diry=1; yf=0; yt=widy+1; }
if (z3>z1) { dirz=-1; zf=widz; zt=-1; } else { dirz=1; zf=0; zt=widz+1; }
for (x=xf; x!=xt; x+=dirx)
for (y=yf; y!=yt; y+=diry)
for (z=zf; z!=zt; z+=dirz)
{
if (copy)
{
copycell(sheet,x1+x,y1+y,z1+z,sheet,x3+x,y3+y,z3+z);
}
else
{
if (x1+x<sheet->dimx && y1+y<sheet->dimy && z1+z<sheet->dimz)
{
resize(sheet,x3+x,y3+y,z3+z);
CELL_AT(sheet,x3+x,y3+y,z3+z)=CELL_AT(sheet,x1+x,y1+y,z1+z);
CELL_AT(sheet,x1+x,y1+y,z1+z)=NULLCELL;
}
else
{
freecell(sheet,x3+x,y3+y,z3+z);
}
}
}
sheet->changed=1;
cachelabels(sheet);
forceupdate(sheet);
}
/*}}}*/
/* sortblock -- sort a block */ /*{{{*/
/* Notes */ /*{{{*/
/*
The idea is to sort a block of cells in one direction by swapping the
planes which are canonical to the sort key vectors. An example is to
sort a two dimensional block line-wise with one column as sort key.
You can have multiple sort keys which all have the same direction and
you can sort a cube plane-wise.
*/
/*}}}*/
const char *sortblock(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, Direction dir, Sortkey *sk, size_t sklen)
{
/* variables */ /*{{{*/
int x,y,z;
int incx=0,incy=0,incz=0;
int distx,disty,distz;
int i,r=-3,norel,work;
/*}}}*/
/* asserts */ /*{{{*/
assert(sklen>0);
assert(x1>=0);
assert(x2>=0);
assert(y1>=0);
assert(y2>=0);
assert(z1>=0);
assert(z2>=0);
/*}}}*/
norel=0;
posorder(&x1,&x2);
posorder(&y1,&y2);
posorder(&z1,&z2);
distx=(x2-x1+1);
disty=(y2-y1+1);
distz=(z2-z1+1);
switch (dir)
{
case IN_X: incx=1; --x2; incy=0; incz=0; distx=1; break;
case IN_Y: incx=0; incy=1; --y2; incz=0; disty=1; break;
case IN_Z: incx=0; incy=0; incz=1; --z2; distz=1; break;
default: assert(0);
}
assert(incx || incy || incz);
do
{
work=0;
for (x=x1,y=y1,z=z1; x<=x2&&y<=y2&&z<=z2; x+=incx,y+=incy,z+=incz)
{
for (i=0; i<sklen; ++i)
{
r=cmpcell(sheet,x+sk[i].x,y+sk[i].y,z+sk[i].z,sheet,x+incx+sk[i].x,y+incy+sk[i].y,z+incz+sk[i].z,sk[i].sortkey);
if (r==2) norel=1;
else if (r==-1 || r==1) break;
else assert(r==0);
}
if (r==1)
{
swapblock(sheet,dir==IN_X ? x : x1,dir==IN_Y ? y : y1,dir==IN_Z ? z : z1,sheet,dir==IN_X ? x+incx : x1,dir==IN_Y ? y+incy : y1,dir==IN_Z ? z+incz : z1,distx,disty,distz);
work=1;
}
}
x2-=incx;
y2-=incy;
z2-=incz;
} while (work);
cachelabels(sheet);
forceupdate(sheet);
sheet->changed=1;
if (norel) return _("uncomparable elements");
else return (const char*)0;
}
/*}}}*/
/* mirrorblock -- mirror a block */ /*{{{*/
void mirrorblock(Sheet *sheet, int x1, int y1, int z1, int x2, int y2, int z2, Direction dir)
{
switch (dir)
{
case IN_X: /* left-right */ /*{{{*/
{
int x,middle=(x2-x1+1)/2;
for (x=0; x<middle; ++x)
{
swapblock(sheet,x1+x,y1,z1,sheet,x2-x,y1,z1, 1,y2-y1+1,z2-z1+1);
}
break;
}
/*}}}*/
case IN_Y: /* upside-down */ /*{{{*/
{
int y,middle=(y2-y1+1)/2;
for (y=0; y<middle; ++y)
{
swapblock(sheet,x1,y1+y,z1,sheet,x1,y2-y,z1, x2-x1+1,1,z2-z1+1);
}
break;
}
/*}}}*/
case IN_Z: /* front-back */ /*{{{*/
{
int z,middle=(z2-z1+1)/2;
for (z=0; z<middle; ++z)
{
swapblock(sheet,x1,y1,z1+z,sheet,x1,y1,z2-z, x2-x1+1,y2-y1+1,1);
}
break;
}
/*}}}*/
default: assert(0);
}
sheet->changed=1;
cachelabels(sheet);
forceupdate(sheet);
}
/*}}}*/