sie@fulcrum.bt.co.uk (Simon Raybould) (03/20/91)
#!/bin/sh # this is scshr.03 (part 3 of a multipart archive) # do not concatenate these parts, unpack them in order with /bin/sh # file gram.y continued # if test ! -r _shar_seq_.tmp; then echo 'Please unpack part 1 first!' exit 1 fi (read Scheck if test "$Scheck" != 3; then echo Please unpack part "$Scheck" next! exit 1 else exit 0 fi ) < _shar_seq_.tmp || exit 1 if test ! -f _shar_wnt_.tmp; then echo 'x - still skipping gram.y' else echo 'x - continuing file gram.y' sed 's/^X//' << 'SHAR_EOF' >> 'gram.y' && X | e '#' e { $$ = new ('#', $1, $3); } X ; X expr_list: e { $$ = new(ELIST, ENULL, $1); } X | expr_list ',' e { $$ = new(ELIST, $1, $3); } X ; X range: var ':' var { $$.left = $1; $$.right = $3; } X | RANGE { $$ = $1; } X ; X var: COL NUMBER { $$.vp = lookat($2 , $1); $$.vf = 0;} X | '$' COL NUMBER { $$.vp = lookat($3 , $2); X $$.vf = FIX_COL;} X | COL '$' NUMBER { $$.vp = lookat($3 , $1); X $$.vf = FIX_ROW;} X | '$' COL '$' NUMBER { $$.vp = lookat($4 , $2); X $$.vf = FIX_ROW | FIX_COL;} X | VAR { $$ = $1.left; } X ; X var_or_range: range { $$ = $1; } X | var { $$.left = $1; $$.right = $1; } X ; X num: NUMBER { $$ = (double) $1; } X | FNUMBER { $$ = $1; } X | '-' num { $$ = -$2; } X | '+' num { $$ = $2; } X ; X strarg: STRING { $$ = $1; } X | var { X char *s, *s1; X s1 = $1.vp->label; X if (!s1) X s1 = "NULL_STRING"; X s = xmalloc((unsigned)strlen(s1)+1); X (void) strcpy(s, s1); X $$ = s; X } X ; X setlist : X | setlist setitem X ; X setitem : K_AUTO { setauto(1); } X | K_AUTOCALC { setauto(1); } X | '~' K_AUTO { setauto(0); } X | '~' K_AUTOCALC { setauto(0); } X | '!' K_AUTO { setauto(0); } X | '!' K_AUTOCALC { setauto(0); } X | K_BYCOLS { setorder(BYCOLS); } X | K_BYROWS { setorder(BYROWS); } X | K_BYGRAPH { setorder(BYGRAPH); } X | K_NUMERIC { numeric = 1; } X | '!' K_NUMERIC { numeric = 0; } X | K_PRESCALE { prescale = 0.01; } X | '!' K_PRESCALE { prescale = 1.0; } X | K_EXTFUN { extfunc = 1; } X | '!' K_EXTFUN { extfunc = 0; } X | K_CELLCUR { showcell = 1; } X | '!' K_CELLCUR { showcell = 0; } X | K_TOPROW { showtop = 1; } X | '!' K_TOPROW { showtop = 0; } X | K_ITERATIONS '=' NUMBER { setiterations($3); } X | K_TBLSTYLE '=' NUMBER { tbl_style = $3; } X | K_TBLSTYLE '=' K_TBL { tbl_style = TBL; } X | K_TBLSTYLE '=' K_LATEX { tbl_style = LATEX; } X | K_TBLSTYLE '=' K_TEX { tbl_style = TEX; } X ; SHAR_EOF echo 'File gram.y is complete' && chmod 0666 gram.y || echo 'restore of gram.y failed' Wc_c="`wc -c < 'gram.y'`" test 12082 -eq "$Wc_c" || echo 'gram.y: original size 12082, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= help.c ============== if test -f 'help.c' -a X"$1" != X"-c"; then echo 'x - skipping help.c (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting help.c (Text)' sed 's/^X//' << 'SHAR_EOF' > 'help.c' && /* X * Help functions for sc X * R. Bond, 1988 X * $Revision: 6.8 $ X */ X #include <curses.h> #include "sc.h" X char *intro[] = { " ", " Overview:", " ", " A: This overview", " B: Options", " C: Cursor movement commands", " D: Cell entry and editing commands", " E: Line Editing", " F: File commands", " G: Row and column commands", " H: Range commands", " I: Miscellaneous commands", " J: Variable names/Expressions", " K: Range functions", " L: Numeric functions", " M: String functions", " N: Financial functions", " O: Time and date functions", " ", " Q: Return to main spreadsheet", (char *)0 }; X char *options[] = { " ", " B: Options", " ", " ^To Toggle options. Toggle one option selected by o:", " ", " a Recalculate automatically or on ``@'' commands.", " c Current cell highlighting enable/disable.", " e External function execution enable/disable.", " n If enabled, a digit starts a numeric value.", " t Top line display enable/disable.", " x Encrypt/decrypt database and listing files.", " $ Dollar prescale. If enabled, all numeric constants.", " (not expressions) entered are multipled by 0.01.", " ", " S Set options. Options include:", " ", " byrows Recalculate in row order. (default)", " bycols Recalculate in column order.", " iterations=n Set the number of iterations allowed. (10)", " tblstyle=xx Set ``T'' output style to:", " 0 (none), tex, latex, or tbl.", (char *)0 }; X char *cursor[] = { " ", " C: Cell cursor movement (always OK):", " ", " ^N ^P ^B ^F Down, up, back, forward", " ^Ed Go to end of range. Follow ^E by a direction indicator", " such as ^P or j.", " Arrow keys (if the terminal and termcap support them.)", " ", " Cell cursor movement if no prompt active:", " j,k,l,h Down, up, right, left", " SPACE Forward", " ^H Back", " TAB Forward, otherwise starts/ends a range", " ^ Up to row 0 of the current column.", " # Down to the last valid row of the current column.", " 0 Back to column A. Preface with ^U if numeric mode.", " $ Forward to the last valid column of the current row.", " b Back then up to the previous valid cell.", " w Forward then down to the next valid cell.", " g Go to a cell. Cell name, range name, quoted string,", " or a number specify which cell.", (char *)0 }; X X char *cell[] = { " ", " D: Cell entry and editing commands:", " ", " = Enter a numeric constant or expression.", " < Enter a left justified string or string expression.", " \",> Enter a right justified string or string expression.", " e Edit the current cell's numeric value.", " E Edit the current cell's string part.", " x Clear the current cell.", " c Copy the last marked cell to the current cell.", " m Mark a cell to be used as the source for ``c''", " + Increment numeric part", " - Decrement numeric part", " ", " In numeric mode, a decimal digit, ``+'', ``-'', and ``.'' all start", " a new numeric constant or expression.", (char *)0 }; X X char *vi[] = { " ", " E: Line Editor", " ", " Hitting the ESC key while entering any command on the top line", " will start a one-line vi-style editor. Supported commands:", " ", " ESC q Abort command entry.", " h l Move cursor forward, backward.", " 0 $ Move cursor to the beginning, end of the line.", " b w Move cursor forward/back one word.", " fc Move cursor to character c.", " tc Move the cursor the the character before c.", " i a Enter insert mode before/after the cursor.", " I Move to cursor column 0 and enter insert mode.", " x X Delete the character under/before the cursor.", " rc Replace the character under the cursor with c.", " cm Change - m = b,f,h,l,t or w.", " dm Delete - m = b,f,h,l,t or w.", " R Enter replace (overstrike) mode.", " + j - k / Forward/backward/search the command history.", " n Repeat last history search.", " . u Repeat/undo the last command.", (char *)0 }; X char *file[] = { " ", " F: File commands:", " ", " G Get a new database from a file. ", " M Merge a new file into the current database.", " P Put the current database into a file.", " W Write a listing of the current database into a file in", " a form that matches its appearance on the screen.", " T Write a listing of the current database to a file, but", " put delimiters between each pair of fields.", " Optionally brackets output with control lines for ``tbl'',", " ``LaTeX'', or ``TeX''.", " ", " If encryption mode is set, file I/O will be encrypted/decrypted.", " ``\"| program\"'' for a file name will pipe (unencrypted) output to", " a program for Put, Write and Table. If a cell name is used", " as the file name, the cell's string part will be used as the", " file name.", (char *)0 }; X X char *row[] = { " ", " G: Row and column commands:", " ", " ir, ic Insert a new, empty row (column)", " ar, ac Append a new copy of the current row (column)", " dr, dc Delete the current row (column)", " pr, pc, pm Pull deleted cells back into the spreadsheet", " Insert rows, columns or merge the cells.", " vr, vc Remove expressions from the affected rows (columns),", " leaving only the values.", " zr, zc Hide (``zap'') the current row (column)", " sr, sc Show hidden rows (columns)", " f Set the output format to be used with the values of", " each cell in this column. Enter field width and", " number of fractional digits. A preceding count can be", " used to change more than one column.", " ", " Commands which move or copy cells also modify the row and column ", " references in the new cell expressions. Use ``fixed'' or the", " ``$'' style cell reference to supress the change.", (char *)0 }; X X char *range[] = { " ", " H: Range commands:", " ", " /x Clear a range. ", " /v Remove the expressions from a range of cells, leaving ", " just the values.", " /c Copy a source range to a destination range.", " /f Fill a range with constant values starting with a given", " value and increasing by a given increment.", " /d Assign a name to a cell or a range of cells. Give the", " the name, surrounded by quotes, and either a cell name such", " as ``A10'' or a range such as ``a1:b20''.", " /s Shows the currently defined range names. Pipe output to", " sort, then to less.", " /u Use this command to undefine a previously defined range", " name.", " ", " Range operations affect a rectangular region on the screen", " defined by the upper left and lower right cells in the region.", " A range is specified by giving the cell names separated by ``:'',", " such as ``a20:k52''. Another way to refer to a range is to use", " a name previously defined using ``/d''.", (char *)0 }; X X char *misc[] = { " ", " I: Miscellaneous commands:", " ", " Q q ^C Exit from the program.", " ^G ESC Abort entry of the current command.", " ? Help", " ! Shell escape. Enter a command to run. ``!!'' repeats", " the last command. Just ``!'' starts an interactive shell.", " ^L Redraw the screen.", " ^R Redraw the screen. Highlight cells with values but no", " expressions.", " ^X Redraw the screen. Show formulas, not values.", " @ Recalculate the spreadsheet.", " ^V Type, in the command line, the name of the current cell.", " ^W Type, in the command line, the current cell's expression.", " ^A Type, in the command line, the current cell's numeric value.", " TAB When the character cursor is on the top line TAB can be used", " to start or stop the display of the default range.", (char *)0 }; X char *var[] = { " ", " J: Variable names:", " ", " K20 Row and column can vary on copies.", " $K$20 Row and column stay fixed on copies.", " $K20 Row can vary; column stays fixed on copies.", " K$20 Row stays fixed; column can vary on copies.", " fixed holds following expession fixed on copies.", " Cells and ranges can be given a symbolic name via ``/d''.", " ", " Expressions:", " -e Negation e<=e Less than or equal", " e+e Addition e=e Equal", " e-e Subtraction e!=e Not Equal", " e*e Multiplication e>=e Greater than or equal", " e/e Division e>e Greater than", " e%e Modulo e<e Less than", " e^e Exponentiation e&e Boolean operator AND.", " ~e Boolean operator NOT e|e Boolean operator OR", " e?e1:e2 or @if(e,e1,e2)", " Conditional: If e is non zero then then e1, else e2.", " Terms may be constants, variables, and parenthesized expressions.", (char *)0 }; X char *rangef[] = { " ", " K: Range functions:", " ", " @sum(r) Sum all valid cells in the range.", " @prod(r) Multiply together all valid cells in the range.", " @avg(r) Average all valid cells in the range.", " @count(r) Count all valid cells in the range.", " @max(r) Return the maximum value in the range.", " @min(r) Return the minimum value in the range.", " @stddev(r) Return the sample standard deviation of ", " the cells in the range.", " @index(e,r) @stindex(e,r)", " Return the numeric (string) value of the cell at", " index e into range r.", " @lookup(e,r) @hlookup(e,r,n) @vlookup(e,r,n)", " Search through the range r for a value that", " matches e. If e is numeric, the last value <= e", " matches; if string, an exact match is required.", " @lookup searches a single row (column) and returns", " the value from the next column (row); @hlookup", " (@vlookup) searches the first row (column) in r and", " returns the value n columns (rows) from the match.", (char *)0 }; X char *numericf[] = { " ", " L: Numeric functions:", " ", " @atan2(e1,e2) Arc tangent of e1/e2.", " @ceil(e) Smallest integer not less than e.", " @eqs(se1,se2) 1 if string expr se1 has the same value as se2.", " @exp(e) Exponential function of e.", " @abs(e) @fabs(e) Absolute value of e.", " @floor(e) The largest integer not greater than e.", " @hypot(x,y) Sqrt(x*x+y*y).", " @max(e1,e2,...) The maximum of the values of the e's.", " @min(e1,e2,...) The minimum of the values of the e's", " @nval(se,e) The numeric value of a named cell.", " pi A constant quite close to pi.", " @pow(e1,e2) e1 raised to the power of e2.", " @rnd(e) Round e to the nearest integer.", " @round(e,n) Round e to n decimal places.", " @sqrt(e) Square root of e.", " @ston(se) Convert string expr se to a numeric", " @ln(e) @log(e) Natural/base 10 logarithm of e.", " @dtr(e) @rtd(e) Convert degrees to/from radians.", " @cos(e) @sin(e) @tan(e) Trig functions of radian arguments.", " @asin(e) @acos(e) @atan(e) Inverse trig function.", (char *)0 }; X char *stringf[] = { " ", " M: String functions:", " ", " # Concatenate strings. For example, the", " string expression ``A0 # \"zy dog\"'' yields", " ``the lazy dog'' if A0 is ``the la''.", " @substr(se,e1,e2) Extract characters e1 through e2 from the", " string expression se. For example,", " ``@substr(\"Nice jacket\" 4, 7)'' yields ", " ``e jac''.", " @fmt(se,e) Convert a number to a string using sprintf(3).", " For example, ``@fmt(\"*%6.3f*\",10.5)'' yields", " ``*10.500*''. Use formats are e, E, f, g, and G.", " @sval(se,e) Return the string value of a cell selected by name.", " @ext(se,e) Call an external function (program or", " script). Convert e to a string and append it", " to the command line as an argument. @ext yields", " a string: the first line printed to standard", " output by the command.", " String expressions are made up of constant strings (characters", " surrounded by quotes), variables, and string functions.", (char *)0 }; X X char *finf[] = { " ", " N: Financial functions:", " ", " @pmt(e1,e2,e3) @pmt(60000,.01,360) computes the monthly", " payments for a $60000 mortgage at 12%", " annual interest (.01 per month) for 30", " years (360 months).", " ", " @fv(e1,e2,e3) @fv(100,.005,36) computes the future value", " of 36 monthly payments of $100 at 6%", " interest (.005 per month). It answers the", " question: ``How much will I have in 36", " months if I deposit $100 per month in a", " savings account paying 6% interest com-", " pounded monthly?''", " ", " @pv(e1,e2,e3) @pv(1000,.015,36) computes the present", " value of an ordinary annuity of 36", " monthly payments of $1000 at 18% annual", " interest. It answers the question: ``How", " much can I borrow at 18% for 30 years if I", " pay $1000 per month?''", (char *)0 }; X X char *timef[] = { " ", " O: Time and date functions:", " ", " @now Return the time encoded in seconds since 1970.", " @dts(m,d,y) Return m/d/y encoded in seconds since 1970.", " @tts(h,m,s) Return h:m:s encoded in seconds since midnight.", " ", " All of the following take an argument expressed in seconds:", " ", " @date(e) Convert the time in seconds to a date", " string 24 characters long in the following", " form: ``Sun Sep 16 01:03:52 1973''. Note", " that you can extract pieces of this fixed format", " string with @substr.", " @year(e) Return the year. Valid years begin with 1970.", " @month(e) Return the month: 1 (Jan) to 12 (Dec).", " @day(e) Return the day of the month: 1 to 31.", " @hour(e) Return the number of hours since midnight: 0 to 23.", " @minute(e) Return the number of minutes since the", " last full hour: 0 to 59.", " @second(e) Return the number of seconds since the", " last full minute: 0 to 59.", (char *)0 }; void help() { X int option; X char **ns = intro; X X while((option = pscreen(ns)) != 'q' && option != 'Q') { X switch (option) { X case 'a': case 'A': ns = intro; break; X case 'b': case 'B': ns = options; break; X case 'c': case 'C': ns = cursor; break; X case 'd': case 'D': ns = cell; break; X case 'e': case 'E': ns = vi; break; X case 'f': case 'F': ns = file; break; X case 'g': case 'G': ns = row; break; X case 'h': case 'H': ns = range; break; X case 'i': case 'I': ns = misc; break; X case 'j': case 'J': ns = var; break; X case 'k': case 'K': ns = rangef; break; X case 'l': case 'L': ns = numericf; break; X case 'm': case 'M': ns = stringf; break; X case 'n': case 'N': ns = finf; break; X case 'o': case 'O': ns = timef; break; X default: ns = intro; break; X } X } X FullUpdate++; X (void) move(1,0); X (void) clrtobot(); } X pscreen(screen) char *screen[]; { X int line; X int dbline; X X (void) move(1,0); X (void) clrtobot(); X dbline = 1; X for (line = 0; screen[line]; line++) { X (void) move(dbline++, 4); X (void) addstr (screen[line]); X (void) clrtoeol(); X } X (void) move(0,0); X (void) printw("Which Screen? [a-n, q]"); X (void) clrtoeol(); X (void) refresh(); X return(nmgetch()); } SHAR_EOF chmod 0666 help.c || echo 'restore of help.c failed' Wc_c="`wc -c < 'help.c'`" test 16990 -eq "$Wc_c" || echo 'help.c: original size 16990, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= interp.c ============== if test -f 'interp.c' -a X"$1" != X"-c"; then echo 'x - skipping interp.c (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting interp.c (Text)' sed 's/^X//' << 'SHAR_EOF' > 'interp.c' && /* SC A Spreadsheet Calculator X * Expression interpreter and assorted support routines. X * X * original by James Gosling, September 1982 X * modified by Mark Weiser and Bruce Israel, X * University of Maryland X * X * More mods Robert Bond, 12/86 X * More mods by Alan Silverstein, 3-4/88, see list of changes. X * $Revision: 6.8 $ X */ X #define DEBUGDTS 1 /* REMOVE ME */ /* #define EXPRTREE /* expr. dependency tree stuff, not ready yet */ X #ifdef aiws #undef _C_func /* Fixes for undefined symbols on AIX */ #endif X #ifdef IEEE_MATH #include <ieeefp.h> #endif /* IEEE_MATH */ X #include <math.h> #ifdef AMIGA double hypot(); #endif /* AMIGA */ #include <signal.h> #include <setjmp.h> #include <stdio.h> X extern int errno; /* set by math functions */ #ifdef BSD42 #include <strings.h> #include <sys/time.h> #ifndef strchr #define strchr index #endif #else #include <time.h> #ifndef SYSIII #include <string.h> #endif #endif X #include <curses.h> #include "sc.h" X #if defined(BSD42) || defined(BSD43) char *re_comp(); #endif #if defined(SYSV2) || defined(SYSV3) char *regcmp(); char *regex(); #endif X #ifdef SIGVOID X void quit(); #else X int quit(); #endif X /* Use this structure to save the the last 'g' command */ X struct go_save { X int g_type; X double g_n; X char *g_s; X int g_row; X int g_col; } gs; X /* g_type can be: */ X #define G_NONE 0 /* Starting value - must be 0*/ #define G_NUM 1 #define G_STR 2 #define G_CELL 3 X #define ISVALID(r,c) ((r)>=0 && (r)<maxrows && (c)>=0 && (c)<maxcols) X extern FILE *popen(); X jmp_buf fpe_save; int exprerr; /* Set by eval() and seval() if expression errors */ double prescale = 1.0; /* Prescale for constants in let() */ int extfunc = 0; /* Enable/disable external functions */ int loading = 0; /* Set when readfile() is active */ double fn1_eval(); double fn2_eval(); struct ent *firstev = (struct ent *)0; /* first expr in the eval list */ X #define PI (double)3.14159265358979323846 #define dtr(x) ((x)*(PI/(double)180.0)) #define rtd(x) ((x)*(180.0/(double)PI)) X double finfunc(fun,v1,v2,v3) int fun; double v1,v2,v3; { X double answer,p; X X p = fn2_eval(pow, 1 + v2, v3); X X switch(fun) X { X case PV: X answer = v1 * (1 - 1/p) / v2; X break; X case FV: X answer = v1 * (p - 1) / v2; X break; X case PMT: X answer = v1 * v2 / (1 - 1/p); X break; X default: X error("Unknown function in finfunc"); X return((double)0); X } X return(answer); } X char * dostindex( val, minr, minc, maxr, maxc) double val; int minr, minc, maxr, maxc; { X register r,c; X register struct ent *p; X char *pr; X int x; X X x = (int) val; X r = minr; c = minc; X p = (struct ent *)0; X if ( minr == maxr ) { /* look along the row */ X c = minc + x - 1; X if (c <= maxc && c >=minc) X p = *ATBL(tbl, r, c); X } else if ( minc == maxc ) { /* look down the column */ X r = minr + x - 1; X if (r <= maxr && r >=minr) X p = *ATBL(tbl, r, c); X } else { X error ("range specified to @stindex"); X return((char *)0); X } X X if (p && p->label) { X pr = xmalloc((unsigned)(strlen(p->label)+1)); X (void)strcpy(pr, p->label); X return (pr); X } else X return((char *)0); } X double doindex( val, minr, minc, maxr, maxc) double val; int minr, minc, maxr, maxc; { X double v; X register r,c; X register struct ent *p; X int x; X X x = (int) val; X v = (double)0; X r = minr; c = minc; X if ( minr == maxr ) { /* look along the row */ X c = minc + x - 1; X if (c <= maxc && c >=minc X && (p = *ATBL(tbl, r, c)) && p->flags&is_valid ) X return p->v; X } X else if ( minc == maxc ){ /* look down the column */ X r = minr + x - 1; X if (r <= maxr && r >=minr X && (p = *ATBL(tbl, r, c)) && p->flags&is_valid ) X return p->v; X } X else error(" range specified to @index"); X return v; } X double dolookup( val, minr, minc, maxr, maxc, offr, offc) struct enode * val; int minr, minc, maxr, maxc, offr, offc; { X double v, ret = (double)0; X register r,c; X register struct ent *p = (struct ent *)0; X int incr,incc,fndr,fndc; X char *s; X X incr = (offc != 0); incc = (offr != 0); X if (etype(val) == NUM) { X v = eval(val); X for (r = minr, c = minc; r <= maxr && c <= maxc; r+=incr, c+=incc) { X if ( (p = *ATBL(tbl, r, c)) && p->flags&is_valid ) { X if (p->v <= v) { X fndr = incc ? (minr + offr) : r; X fndc = incr ? (minc + offc) : c; X if (ISVALID(fndr,fndc)) X p = *ATBL(tbl, fndr, fndc); X else error(" range specified to @[hv]lookup"); X if ( p && p->flags&is_valid) X ret = p->v; X } else break; X } X } X } else { X s = seval(val); X for (r = minr, c = minc; r <= maxr && c <= maxc; r+=incr, c+=incc) { X if ( (p = *ATBL(tbl, r, c)) && p->label ) { X if (strcmp(p->label,s) == 0) { X fndr = incc ? (minr + offr) : r; X fndc = incr ? (minc + offc) : c; X if (ISVALID(fndr,fndc)) X p = *ATBL(tbl, fndr, fndc); X else error(" range specified to @[hv]lookup"); X break; X } X } X } X if ( p && p->flags&is_valid) X ret = p->v; X xfree(s); X } X return ret; } X double docount(minr, minc, maxr, maxc) int minr, minc, maxr, maxc; { X int v; X register r,c; X register struct ent *p; X X v = 0; X for (r = minr; r<=maxr; r++) X for (c = minc; c<=maxc; c++) X if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid) X v++; X return v; } X double dosum(minr, minc, maxr, maxc) int minr, minc, maxr, maxc; { X double v; X register r,c; X register struct ent *p; X X v = (double)0; X for (r = minr; r<=maxr; r++) X for (c = minc; c<=maxc; c++) X if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid) X v += p->v; X return v; } X double doprod(minr, minc, maxr, maxc) int minr, minc, maxr, maxc; { X double v; X register r,c; X register struct ent *p; X X v = 1; X for (r = minr; r<=maxr; r++) X for (c = minc; c<=maxc; c++) X if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid) X v *= p->v; X return v; } X double doavg(minr, minc, maxr, maxc) int minr, minc, maxr, maxc; { X double v; X register r,c,count; X register struct ent *p; X X v = (double)0; X count = 0; X for (r = minr; r<=maxr; r++) X for (c = minc; c<=maxc; c++) X if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid) { X v += p->v; X count++; X } X X if (count == 0) X return ((double) 0); X X return (v / (double)count); } X double dostddev(minr, minc, maxr, maxc) int minr, minc, maxr, maxc; { X double lp, rp, v, nd; X register r,c,n; X register struct ent *p; X X n = 0; X lp = 0; X rp = 0; X for (r = minr; r<=maxr; r++) X for (c = minc; c<=maxc; c++) X if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid) { X v = p->v; X lp += v*v; X rp += v; X n++; X } X X if ((n == 0) || (n == 1)) X return ((double) 0); X nd = (double)n; X return (sqrt((nd*lp-rp*rp)/(nd*(nd-1)))); } X double domax(minr, minc, maxr, maxc) int minr, minc, maxr, maxc; { X double v = (double)0; X register r,c,count; X register struct ent *p; X X count = 0; X for (r = minr; r<=maxr; r++) X for (c = minc; c<=maxc; c++) X if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid) { X if (!count) { X v = p->v; X count++; X } else if (p->v > v) X v = p->v; X } X X if (count == 0) X return ((double) 0); X X return (v); } X double domin(minr, minc, maxr, maxc) int minr, minc, maxr, maxc; { X double v = (double)0; X register r,c,count; X register struct ent *p; X X count = 0; X for (r = minr; r<=maxr; r++) X for (c = minc; c<=maxc; c++) X if ((p = *ATBL(tbl, r, c)) && p->flags&is_valid) { X if (!count) { X v = p->v; X count++; X } else if (p->v < v) X v = p->v; X } X X if (count == 0) X return ((double) 0); X X return (v); } X #define sec_min 60 #define sec_hr 3600L #define sec_day 86400L #define sec_yr 31471200L /* 364.25 days/yr */ #define sec_mo 2622600L /* sec_yr/12: sort of an average */ int mdays[12]={ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; X double dodts(mo, day, yr) int mo, day, yr; { X long trial; X register struct tm *tp; X register int i; X register long jdate; X X mdays[1] = 28 + (yr%4 == 0); X X if (mo < 1 || mo > 12 || day < 1 || day > mdays[--mo] || X yr > 1999 || yr < 1970) { X error("@dts: invalid argument"); X return(0.0); X } X X jdate = day-1; X for (i=0; i<mo; i++) X jdate += mdays[i]; X for (i = 1970; i < yr; i++) X jdate += 365 + (i%4 == 0); X X trial = jdate * sec_day; X X yr -= 1900; X X tp = localtime(&trial); X X if (tp->tm_year != yr) { X /* X * We may fail this test once a year because of time zone X * and daylight savings time errors. This bounces the X * trial time past the boundary. The error introduced is X * corrected below. X */ X trial += sec_day*(yr - tp->tm_year); X tp = localtime(&trial); X } X if (tp->tm_mon != mo) { X /* We may fail this test once a month. */ X trial += sec_day*(mo - tp->tm_mon); X tp = localtime(&trial); X } X if (tp->tm_mday + tp->tm_hour + tp->tm_min + tp->tm_sec != day) { X trial -= (tp->tm_mday - day)*sec_day + tp->tm_hour*sec_hr X + tp->tm_min*sec_min + tp->tm_sec; X } X #ifdef DEBUGDTS X tp = localtime(&trial); X if (tp->tm_mday + tp->tm_hour + tp->tm_min + tp->tm_sec + X tp->tm_year + tp->tm_mon != yr+mo+day) X error("Dts broke down"); #endif X X return ((double)trial); } X double dotts(hr, min, sec) int hr, min, sec; { X if (hr < 0 || hr > 23 || min < 0 || min > 59 || sec < 0 || sec > 59) { X error ("@tts: Invalid argument"); X return ((double)0); X } X return ((double)(sec+min*60+hr*3600)); } X double dotime(which, when) int which; double when; { X long time(); X X static long t_cache; X static struct tm tm_cache; X struct tm *tp; X long tloc; X X if (which == NOW) X return (double)time((long *)0); X X tloc = (long)when; X X if (tloc != t_cache) { X tp = localtime(&tloc); X tm_cache = *tp; X tm_cache.tm_mon += 1; X tm_cache.tm_year += 1900; X t_cache = tloc; X } X X switch (which) { X case HOUR: return((double)(tm_cache.tm_hour)); X case MINUTE: return((double)(tm_cache.tm_min)); X case SECOND: return((double)(tm_cache.tm_sec)); X case MONTH: return((double)(tm_cache.tm_mon)); X case DAY: return((double)(tm_cache.tm_mday)); X case YEAR: return((double)(tm_cache.tm_year)); X } X /* Safety net */ X return ((double)0); } X double doston(s) char *s; { X char *strtof(); X double v; X X if (!s) X return((double)0); X X (void)strtof(s, &v); X xfree(s); X return(v); } X double doeqs(s1, s2) char *s1, *s2; { X double v; X X if (!s1 && !s2) X return((double)1.0); X X if (!s1 || !s2) X v = 0.0; X else if (strcmp(s1, s2) == 0) X v = 1.0; X else X v = 0.0; X X if (s1) X xfree(s1); X X if (s2) X xfree(s2); X X return(v); } X X /* X * Given a string representing a column name and a value which is a column X * number, return a pointer to the selected cell's entry, if any, else 0. Use X * only the integer part of the column number. Always free the string. X */ X struct ent * getent (colstr, rowdoub) X char *colstr; X double rowdoub; { X int collen; /* length of string */ X int row, col; /* integer values */ X struct ent *ep = (struct ent *)0; /* selected entry */ X X if (((row = (int) floor (rowdoub)) >= 0) X && (row < maxrows) /* in range */ X && ((collen = strlen (colstr)) <= 2) /* not too long */ X && ((col = atocol (colstr, collen)) >= 0) X && (col < maxcols)) /* in range */ X { X ep = *ATBL(tbl, row, col); X } X X xfree (colstr); X return (ep); } X X /* X * Given a string representing a column name and a value which is a column X * number, return the selected cell's numeric value, if any. X */ X double donval (colstr, rowdoub) X char *colstr; X double rowdoub; { X struct ent *ep; X X return (((ep = getent (colstr, rowdoub)) && ((ep -> flags) & is_valid)) ? X (ep -> v) : (double)0); } X X /* X * The list routines (e.g. dolmax) are called with an LMAX enode. X * The left pointer is a chain of ELIST nodes, the right pointer X * is a value. X */ double dolmax(ep) struct enode *ep; { X register int count = 0; X register double maxval = 0; /* Assignment to shut up lint */ X register struct enode *p; X register double v; X X for (p = ep; p; p = p->e.o.left) { X v = eval(p->e.o.right); X if (!count || v > maxval) { X maxval = v; count++; X } X } X if (count) return maxval; X else return (double)0; } X double dolmin(ep) struct enode *ep; { X register int count = 0; X register double minval = 0; /* Assignment to shut up lint */ X register struct enode *p; X register double v; X X for (p = ep; p; p = p->e.o.left) { X v = eval(p->e.o.right); X if (!count || v < minval) { X minval = v; count++; X } X } X if (count) return minval; X else return (double)0; } X double eval(e) register struct enode *e; { X if (e == (struct enode *)0) return (double)0; X switch (e->op) { X case '+': return (eval(e->e.o.left) + eval(e->e.o.right)); X case '-': return (eval(e->e.o.left) - eval(e->e.o.right)); X case '*': return (eval(e->e.o.left) * eval(e->e.o.right)); X case '/': return (eval(e->e.o.left) / eval(e->e.o.right)); X case '%': { double num, denom; X num = floor(eval(e->e.o.left)); X denom = floor(eval (e->e.o.right)); X return denom ? num - floor(num/denom)*denom : (double)0; } X case '^': return (fn2_eval(pow,eval(e->e.o.left),eval(e->e.o.right))); X case '<': return (eval(e->e.o.left) < eval(e->e.o.right)); X case '=': return (eval(e->e.o.left) == eval(e->e.o.right)); X case '>': return (eval(e->e.o.left) > eval(e->e.o.right)); X case '&': return (eval(e->e.o.left) && eval(e->e.o.right)); X case '|': return (eval(e->e.o.left) || eval(e->e.o.right)); X case IF: X case '?': return eval(e->e.o.left) ? eval(e->e.o.right->e.o.left) X : eval(e->e.o.right->e.o.right); X case 'm': return (-eval(e->e.o.right)); X case 'f': return (eval(e->e.o.right)); X case '~': return (eval(e->e.o.right) == 0.0); X case 'k': return (e->e.k); X case 'v': return (e->e.v.vp->v); X case INDEX: X case LOOKUP: X case HLOOKUP: X case VLOOKUP: X { register r,c; X register maxr, maxc; X register minr, minc; X maxr = e->e.o.right->e.r.right.vp -> row; X maxc = e->e.o.right->e.r.right.vp -> col; X minr = e->e.o.right->e.r.left.vp -> row; X minc = e->e.o.right->e.r.left.vp -> col; X if (minr>maxr) r = maxr, maxr = minr, minr = r; X if (minc>maxc) c = maxc, maxc = minc, minc = c; X switch(e->op){ X case LOOKUP: X return dolookup(e->e.o.left, minr, minc, maxr, maxc, X minr==maxr, minc==maxc); X case HLOOKUP: X return dolookup(e->e.o.left->e.o.left, minr,minc,maxr,maxc, X (int) eval(e->e.o.left->e.o.right), 0); X case VLOOKUP: X return dolookup(e->e.o.left->e.o.left, minr,minc,maxr,maxc, X 0, (int) eval(e->e.o.left->e.o.right)); X case INDEX: X return doindex(eval(e->e.o.left), minr, minc, maxr, maxc); X } X } X case REDUCE | '+': X case REDUCE | '*': X case REDUCE | 'a': X case REDUCE | 'c': X case REDUCE | 's': X case REDUCE | MAX: X case REDUCE | MIN: X { register r,c; X register maxr, maxc; X register minr, minc; X maxr = e->e.r.right.vp -> row; X maxc = e->e.r.right.vp -> col; X minr = e->e.r.left.vp -> row; X minc = e->e.r.left.vp -> col; X if (minr>maxr) r = maxr, maxr = minr, minr = r; X if (minc>maxc) c = maxc, maxc = minc, minc = c; X switch (e->op) { X case REDUCE | '+': return dosum(minr, minc, maxr, maxc); X case REDUCE | '*': return doprod(minr, minc, maxr, maxc); X case REDUCE | 'a': return doavg(minr, minc, maxr, maxc); X case REDUCE | 'c': return docount(minr, minc, maxr, maxc); X case REDUCE | 's': return dostddev(minr, minc, maxr, maxc); X case REDUCE | MAX: return domax(minr, minc, maxr, maxc); X case REDUCE | MIN: return domin(minr, minc, maxr, maxc); X } X } X case ABS: return (fn1_eval( fabs, eval(e->e.o.right))); X case ACOS: return (fn1_eval( acos, eval(e->e.o.right))); X case ASIN: return (fn1_eval( asin, eval(e->e.o.right))); X case ATAN: return (fn1_eval( atan, eval(e->e.o.right))); X case ATAN2: return (fn2_eval( atan2, eval(e->e.o.left), eval(e->e.o.right))); X case CEIL: return (fn1_eval( ceil, eval(e->e.o.right))); X case COS: return (fn1_eval( cos, eval(e->e.o.right))); X case EXP: return (fn1_eval( exp, eval(e->e.o.right))); X case FABS: return (fn1_eval( fabs, eval(e->e.o.right))); X case FLOOR: return (fn1_eval( floor, eval(e->e.o.right))); X case HYPOT: return (fn2_eval( hypot, eval(e->e.o.left), eval(e->e.o.right))); X case LOG: return (fn1_eval( log, eval(e->e.o.right))); X case LOG10: return (fn1_eval( log10, eval(e->e.o.right))); X case POW: return (fn2_eval( pow, eval(e->e.o.left), eval(e->e.o.right))); X case SIN: return (fn1_eval( sin, eval(e->e.o.right))); X case SQRT: return (fn1_eval( sqrt, eval(e->e.o.right))); X case TAN: return (fn1_eval( tan, eval(e->e.o.right))); X case DTR: return (dtr(eval(e->e.o.right))); X case RTD: return (rtd(eval(e->e.o.right))); X case RND: { X double temp; X temp = eval(e->e.o.right); X return(temp-floor(temp) < 0.5 ? X floor(temp) : ceil(temp)); X } X case ROUND: { X double temp = eval(e->e.o.left); X int prec = (int) eval(e->e.o.right), scal = 1; X while (prec-- > 0) scal *= 10; X temp *= scal; X temp = ((temp-floor(temp)) < 0.5 ? X floor(temp) : ceil(temp)); X return(temp / scal); X } X case FV: X case PV: X case PMT: return(finfunc(e->op,eval(e->e.o.left), X eval(e->e.o.right->e.o.left), X eval(e->e.o.right->e.o.right))); X case HOUR: return (dotime(HOUR, eval(e->e.o.right))); X case MINUTE: return (dotime(MINUTE, eval(e->e.o.right))); X case SECOND: return (dotime(SECOND, eval(e->e.o.right))); X case MONTH: return (dotime(MONTH, eval(e->e.o.right))); X case DAY: return (dotime(DAY, eval(e->e.o.right))); X case YEAR: return (dotime(YEAR, eval(e->e.o.right))); X case NOW: return (dotime(NOW, (double)0.0)); X case DTS: return (dodts((int)eval(e->e.o.left), X (int)eval(e->e.o.right->e.o.left), X (int)eval(e->e.o.right->e.o.right))); X case TTS: return (dotts((int)eval(e->e.o.left), X (int)eval(e->e.o.right->e.o.left), X (int)eval(e->e.o.right->e.o.right))); X case STON: return (doston(seval(e->e.o.right))); X case EQS: return (doeqs(seval(e->e.o.right),seval(e->e.o.left))); X case LMAX: return dolmax(e); X case LMIN: return dolmin(e); X case NVAL: return (donval(seval(e->e.o.left),eval(e->e.o.right))); X default: error ("Illegal numeric expression"); X exprerr = 1; X } X return((double)0.0); } X #ifdef SIGVOID void #endif eval_fpe(signo) /* Trap for FPE errors in eval */ int signo; { #ifdef IEEE_MATH X (void)fpsetsticky((fp_except)0); /* Clear exception */ #endif /* IEEE_MATH */ X longjmp(fpe_save, 1); } X double fn1_eval(fn, arg) double (*fn)(); double arg; { X double res; X errno = 0; X res = (*fn)(arg); X if(errno) X eval_fpe(0); X X return res; } X double fn2_eval(fn, arg1, arg2) double (*fn)(); double arg1, arg2; { X double res; X errno = 0; X res = (*fn)(arg1, arg2); X if(errno) X eval_fpe(0); X X return res; } X /* X * Rules for string functions: X * Take string arguments which they xfree. X * All returned strings are assumed to be xalloced. X */ X char * docat(s1, s2) register char *s1, *s2; { X register char *p; X char *arg1, *arg2; X X if (!s1 && !s2) X return((char *)0); X arg1 = s1 ? s1 : ""; X arg2 = s2 ? s2 : ""; X p = xmalloc((unsigned)(strlen(arg1)+strlen(arg2)+1)); X (void) strcpy(p, arg1); X (void) strcat(p, arg2); X if (s1) X xfree(s1); X if (s2) X xfree(s2); X return(p); } X char * dodate(tloc) long tloc; { X char *tp; X char *p; X X tp = ctime(&tloc); X tp[24] = '\0'; X p = xmalloc((unsigned)25); X (void) strcpy(p, tp); X return(p); } X X char * dofmt(fmtstr, v) char *fmtstr; double v; { X char buff[FBUFLEN]; X char *p; X X if (!fmtstr) X return((char *)0); X (void)sprintf(buff, fmtstr, v); X p = xmalloc((unsigned)(strlen(buff)+1)); X (void) strcpy(p, buff); X xfree(fmtstr); X return(p); } X X /* X * Given a command name and a value, run the command with the given value and X * read and return its first output line (only) as an allocated string, always X * a copy of prevstr, which is set appropriately first unless external X * functions are disabled, in which case the previous value is used. The X * handling of prevstr and freeing of command is tricky. Returning an X * allocated string in all cases, even if null, insures cell expressions are X * written to files, etc. X */ X #ifdef VMS char * doext(command, value) char *command; double value; { X error("Warning: External functions unavailable on VMS"); X if (command) X xfree(command); X return (strcpy (xmalloc((unsigned) 1), "\0")); } X #else /* VMS */ X char * doext (command, value) char *command; double value; { X static char *prevstr = (char *)0; /* previous result */ X char buff[FBUFLEN]; /* command line/return, not permanently alloc */ X X if (!prevstr) { X prevstr = xmalloc((unsigned)1); X *prevstr = '\0'; X } X if (!extfunc) { X error ("Warning: external functions disabled; using %s value", X prevstr ? "previous" : "null"); X X if (command) xfree (command); X } else { X if (prevstr) xfree (prevstr); /* no longer needed */ X prevstr = '\0'; X X if ((! command) || (! *command)) { X error ("Warning: external function given null command name"); X if (command) xfree (command); X } else { X FILE *pp; X X (void) sprintf (buff, "%s %g", command, value); /* build cmd line */ X xfree (command); X X error ("Running external function..."); X (void) refresh(); X X if ((pp = popen (buff, "r")) == (FILE *) NULL) /* run it */ X error ("Warning: running \"%s\" failed", buff); X else { X if (fgets (buff, sizeof(buff)-1, pp) == NULL) /* one line */ X error ("Warning: external function returned nothing"); X else { X char *cp; X X error (""); /* erase notice */ X buff[sizeof(buff)-1] = '\0'; X X if (cp = strchr (buff, '\n')) /* contains newline */ X *cp = '\0'; /* end string there */ X X (void) strcpy (prevstr = X xmalloc ((unsigned) (strlen (buff) + 1)), buff); X /* save alloc'd copy */ X } X (void) pclose (pp); X X } /* else */ X } /* else */ X } /* else */ X return (strcpy (xmalloc ((unsigned) (strlen (prevstr) + 1)), prevstr)); } X #endif /* VMS */ X X /* X * Given a string representing a column name and a value which is a column X * number, return the selected cell's string value, if any. Even if none, X * still allocate and return a null string so the cell has a label value so X * the expression is saved in a file, etc. X */ X char * dosval (colstr, rowdoub) X char *colstr; X double rowdoub; { X struct ent *ep; X char *label; X X label = (ep = getent (colstr, rowdoub)) ? (ep -> label) : ""; X return (strcpy (xmalloc ((unsigned) (strlen (label) + 1)), label)); } X X /* X * Substring: Note that v1 and v2 are one-based to users, but zero-based X * when calling this routine. X */ X char * dosubstr(s, v1, v2) char *s; register int v1,v2; { X register char *s1, *s2; X char *p; X X if (!s) X return((char *)0); X X if (v2 >= strlen (s)) /* past end */ X v2 = strlen (s) - 1; /* to end */ X X if (v1 < 0 || v1 > v2) { /* out of range, return null string */ X xfree(s); X p = xmalloc((unsigned)1); X p[0] = '\0'; X return(p); X } X s2 = p = xmalloc((unsigned)(v2-v1+2)); X s1 = &s[v1]; X for(; v1 <= v2; s1++, s2++, v1++) X *s2 = *s1; X *s2 = '\0'; X xfree(s); X return(p); } X char * seval(se) register struct enode *se; { X register char *p; X X if (se == (struct enode *)0) return (char *)0; X switch (se->op) { X case O_SCONST: p = xmalloc((unsigned)(strlen(se->e.s)+1)); X (void) strcpy(p, se->e.s); X return(p); X case O_VAR: { X struct ent *ep; X ep = se->e.v.vp; X X if (!ep->label) X return((char *)0); X p = xmalloc((unsigned)(strlen(ep->label)+1)); X (void) strcpy(p, ep->label); X return(p); X } X case '#': return(docat(seval(se->e.o.left), seval(se->e.o.right))); X case 'f': return(seval(se->e.o.right)); X case IF: X case '?': return(eval(se->e.o.left) ? seval(se->e.o.right->e.o.left) X : seval(se->e.o.right->e.o.right)); X case DATE: return(dodate((long)(eval(se->e.o.right)))); X case FMT: return(dofmt(seval(se->e.o.left), eval(se->e.o.right))); X case STINDEX: X { register r,c; X register maxr, maxc; X register minr, minc; X maxr = se->e.o.right->e.r.right.vp -> row; X maxc = se->e.o.right->e.r.right.vp -> col; X minr = se->e.o.right->e.r.left.vp -> row; X minc = se->e.o.right->e.r.left.vp -> col; X if (minr>maxr) r = maxr, maxr = minr, minr = r; X if (minc>maxc) c = maxc, maxc = minc, minc = c; X return dostindex(eval(se->e.o.left), minr, minc, maxr, maxc); X } X case EXT: return(doext(seval(se->e.o.left), eval(se->e.o.right))); X case SVAL: return(dosval(seval(se->e.o.left), eval(se->e.o.right))); X case SUBSTR: return(dosubstr(seval(se->e.o.left), X (int)eval(se->e.o.right->e.o.left) - 1, X (int)eval(se->e.o.right->e.o.right) - 1)); X default: X error ("Illegal string expression"); X exprerr = 1; X return((char *)0); X } } X /* X * The graph formed by cell expressions which use other cells's values is not X * evaluated "bottom up". The whole table is merely re-evaluated cell by cell, X * top to bottom, left to right, in RealEvalAll(). Each cell's expression uses X * constants in other cells. However, RealEvalAll() notices when a cell gets a X * new numeric or string value, and reports if this happens for any cell. X * EvalAll() repeats calling RealEvalAll() until there are no changes or the X * evaluation count expires. X */ X int propagation = 10; /* max number of times to try calculation */ X void setiterations(i) int i; { X if(i<1) { X error("iteration count must be at least 1"); X propagation = 1; X } X else propagation = i; } X void EvalAll () { X int lastcnt, repct = 0; X X while ((lastcnt = RealEvalAll()) && (repct++ <= propagation)); X if((propagation>1)&& (lastcnt >0 )) X error("Still changing after %d iterations",propagation-1); } X /* X * Evaluate all cells which have expressions and alter their numeric or string X * values. Return the number of cells which changed. X */ X int RealEvalAll () { X register int i,j; X int chgct = 0; X register struct ent *p; X X (void) signal(SIGFPE, eval_fpe); #ifdef EXPRTREE X for (p = firstev; p; p = p->evnext) X RealEvalOne(p, &chgct); #else X if(calc_order == BYROWS ) { X for (i=0; i<=maxrow; i++) X for (j=0; j<=maxcol; j++) X if ((p=tbl[i][j]) && p->expr) RealEvalOne(p,i,j, &chgct); X } X else if ( calc_order == BYCOLS ) { X for (j=0; j<=maxcol; j++) X { for (i=0; i<=maxrow; i++) X if ((p=tbl[i][j]) && p->expr) RealEvalOne(p,i,j, &chgct); X } X } X else error("Internal error calc_order"); #endif X X (void) signal(SIGFPE, quit); X return(chgct); } X void #ifdef EXPRTREE RealEvalOne(p, chgct) register struct ent *p; int *chgct; #else RealEvalOne(p, i, j, chgct) register struct ent *p; int i, j, *chgct; #endif { X if (p->flags & is_strexpr) { X char *v; X if (setjmp(fpe_save)) { #ifdef EXPRTREE X error("Floating point exception %s", v_name(p->row, p->col)); #else X error("Floating point exception %s", v_name(i, j)); #endif X v = ""; X } else { X v = seval(p->expr); X } X if (!v && !p->label) /* Everything's fine */ X return; X if (!p->label || !v || strcmp(v, p->label) != 0) { X (*chgct)++; X p->flags |= is_changed; X changed++; X } X if(p->label) X xfree(p->label); X p->label = v; X } else { X double v; X if (setjmp(fpe_save)) { #ifdef EXPRTREE X error("Floating point exception %s", v_name(p->row, p->col)); #else X error("Floating point exception %s", v_name(i, j)); #endif X v = (double)0.0; X } else { X v = eval (p->expr); X } X if (v != p->v) { X p->v = v; (*chgct)++; X p->flags |= is_changed|is_valid; X changed++; X } X } } X struct enode * new(op, a1, a2) int op; struct enode *a1, *a2; { X register struct enode *p; X p = (struct enode *) xmalloc ((unsigned)sizeof (struct enode)); X p->op = op; X p->e.o.left = a1; X p->e.o.right = a2; X return p; } X struct enode * new_var(op, a1) int op; struct ent_ptr a1; { X register struct enode *p; X p = (struct enode *) xmalloc ((unsigned)sizeof (struct enode)); X p->op = op; X p->e.v = a1; X return p; } X struct enode * new_range(op, a1) int op; struct range_s a1; { X register struct enode *p; X p = (struct enode *) xmalloc ((unsigned)sizeof (struct enode)); X p->op = op; X p->e.r = a1; X return p; } X struct enode * new_const(op, a1) int op; double a1; { X register struct enode *p; X p = (struct enode *) xmalloc ((unsigned)sizeof (struct enode)); X p->op = op; X p->e.k = a1; X return p; } X struct enode * new_str(s) char *s; { X register struct enode *p; X X p = (struct enode *) xmalloc ((unsigned)sizeof(struct enode)); X p->op = O_SCONST; X p->e.s = s; X return(p); } X void copy(dv1, dv2, v1, v2) struct ent *dv1, *dv2, *v1, *v2; { X int minsr, minsc; X int maxsr, maxsc; X int mindr, mindc; X int maxdr, maxdc; X int vr, vc; X int r, c; X X mindr = dv1->row; X mindc = dv1->col; X maxdr = dv2->row; X maxdc = dv2->col; X if (mindr>maxdr) r = maxdr, maxdr = mindr, mindr = r; X if (mindc>maxdc) c = maxdc, maxdc = mindc, mindc = c; X maxsr = v2->row; X maxsc = v2->col; X minsr = v1->row; X minsc = v1->col; X if (minsr>maxsr) r = maxsr, maxsr = minsr, minsr = r; X if (minsc>maxsc) c = maxsc, maxsc = minsc, minsc = c; X checkbounds(&maxdr, &maxdc); X X erase_area(mindr, mindc, maxdr, maxdc); X if (minsr == maxsr && minsc == maxsc) { X /* Source is a single cell */ X for(vr = mindr; vr <= maxdr; vr++) X for (vc = mindc; vc <= maxdc; vc++) X copyrtv(vr, vc, minsr, minsc, maxsr, maxsc); X } else if (minsr == maxsr) { X /* Source is a single row */ X for (vr = mindr; vr <= maxdr; vr++) X copyrtv(vr, mindc, minsr, minsc, maxsr, maxsc); X } else if (minsc == maxsc) { X /* Source is a single column */ X for (vc = mindc; vc <= maxdc; vc++) X copyrtv(mindr, vc, minsr, minsc, maxsr, maxsc); X } else { X /* Everything else */ X copyrtv(mindr, mindc, minsr, minsc, maxsr, maxsc); X } X sync_refs(); } X void copyrtv(vr, vc, minsr, minsc, maxsr, maxsc) int vr, vc, minsr, minsc, maxsr, maxsc; { X register struct ent *p; X register struct ent *n; X register int sr, sc; X register int dr, dc; X X for (dr=vr, sr=minsr; sr<=maxsr; sr++, dr++) X for (dc=vc, sc=minsc; sc<=maxsc; sc++, dc++) { X if (p = *ATBL(tbl, sr, sc)) X { n = lookat (dr, dc); X (void) clearent(n); X copyent( n, p, dr - sr, dc - sc); X } X else X if (n = *ATBL(tbl, dr, dc)) X (void) clearent(n); X } } X void eraser(v1, v2) struct ent *v1, *v2; { X FullUpdate++; X flush_saved(); X erase_area(v1->row, v1->col, v2->row, v2->col); X sync_refs(); } X /* Goto subroutines */ X void g_free() { X switch (gs.g_type) { X case G_STR: xfree(gs.g_s); break; X default: break; X } X gs.g_type = G_NONE; } X void go_last() { X switch (gs.g_type) { X case G_NONE: X error("Nothing to repeat"); break; X case G_NUM: X num_search(gs.g_n); X break; X case G_CELL: X moveto(gs.g_row, gs.g_col); X break; X case G_STR: X gs.g_type = G_NONE; /* Don't free the string */ X str_search(gs.g_s); X break; X X default: error("go_last: internal error"); X } } X void moveto(row, col) int row, col; { X currow = row; X curcol = col; X g_free(); X gs.g_type = G_CELL; X gs.g_row = currow; X gs.g_col = curcol; } X void num_search(n) double n; { X register struct ent *p; X register int r,c; X int endr, endc; X X g_free(); X gs.g_type = G_NUM; X gs.g_n = n; X X if (currow > maxrow) X endr = maxrow ? maxrow-1 : 0; X else X endr = currow; X if (curcol > maxcol) X endc = maxcol ? maxcol-1 : 0; X else X endc = curcol; X r = endr; X c = endc; X do { X if (c < maxcol) X c++; X else { X if (r < maxrow) { X while(++r < maxrow && row_hidden[r]) /* */; X c = 0; X } else { X r = 0; X c = 0; X } X } X if (r == endr && c == endc) { X error("Number not found"); X return; X } X p = *ATBL(tbl, r, c); X } while(col_hidden[c] || !p || p && (!(p->flags & is_valid) X || (p->flags&is_valid) && p->v != n)); X currow = r; X curcol = c; } X void str_search(s) char *s; { X register struct ent *p; X register int r,c; X int endr, endc; X char *tmp; X #if defined(BSD42) || defined(BSD43) X if ((tmp = re_comp(s)) != (char *)0) { X xfree(s); X error(tmp); X return; X } #endif #if defined(SYSV2) || defined(SYSV3) X if ((tmp = regcmp(s, (char *)0)) == (char *)0) { X xfree(s); X error("Invalid search string"); X return; X } #endif X g_free(); X gs.g_type = G_STR; X gs.g_s = s; X if (currow > maxrow) X endr = maxrow ? maxrow-1 : 0; X else X endr = currow; X if (curcol > maxcol) X endc = maxcol ? maxcol-1 : 0; X else X endc = curcol; X r = endr; X c = endc; X do { X if (c < maxcol) X c++; X else { X if (r < maxrow) { X while(++r < maxrow && row_hidden[r]) /* */; X c = 0; X } else { X r = 0; X c = 0; X } X } X if (r == endr && c == endc) { X error("String not found"); #if defined(SYSV2) || defined(SYSV3) X free(tmp); #endif X return; X } X p = *ATBL(tbl, r, c); X } while(col_hidden[c] || !p || p && (!(p->label) #if defined(BSD42) || defined(BSD43) X || (re_exec(p->label) == 0))); #else #if defined(SYSV2) || defined(SYSV3) X || (regex(tmp, p->label) == (char *)0))); #else X || (strcmp(s, p->label) != 0))); #endif #endif X currow = r; X curcol = c; #if defined(SYSV2) || defined(SYSV3) X free(tmp); #endif } X void fill (v1, v2, start, inc) struct ent *v1, *v2; double start, inc; { X register r,c; X register struct ent *n; X int maxr, maxc; X int minr, minc; X X maxr = v2->row; X maxc = v2->col; X minr = v1->row; X minc = v1->col; X if (minr>maxr) r = maxr, maxr = minr, minr = r; X if (minc>maxc) c = maxc, maxc = minc, minc = c; X checkbounds(&maxr, &maxc); X if (minr < 0) minr = 0; X if (minr < 0) minr = 0; X X FullUpdate++; X if( calc_order == BYROWS ) { X for (r = minr; r<=maxr; r++) X for (c = minc; c<=maxc; c++) { X n = lookat (r, c); X (void) clearent(n); X n->v = start; X start += inc; X n->flags |= (is_changed|is_valid); X } X } X else if ( calc_order == BYCOLS ) { X for (c = minc; c<=maxc; c++) X for (r = minr; r<=maxr; r++) { X n = lookat (r, c); X (void) clearent(n); X n->v = start; X start += inc; X n->flags |= (is_changed|is_valid); X } X } X else error(" Internal error calc_order"); X changed++; } X void let (v, e) struct ent *v; struct enode *e; { X double val; X X exprerr = 0; X (void) signal(SIGFPE, eval_fpe); X if (setjmp(fpe_save)) { X error ("Floating point exception in cell %s", v_name(v->row, v->col)); X val = (double)0.0; X } else { X val = eval(e); X } X (void) signal(SIGFPE, quit); X if (exprerr) { X efree((struct ent *)0, e); X return; X } X if (constant(e)) { X if (!loading) X v->v = val * prescale; X else X v->v = val; X if (!(v->flags & is_strexpr)) { X efree(v, v->expr); X v->expr = (struct enode *)0; X } X efree((struct ent *)0, e); X v->flags |= (is_changed|is_valid); X changed++; X modflg++; X return; X } X efree (v, v->expr); X v->expr = e; X v->flags |= (is_changed|is_valid); X v->flags &= ~is_strexpr; X #ifdef EXPRTREE X totoptree(v); #endif X changed++; X modflg++; } X void slet (v, se, flushdir) struct ent *v; struct enode *se; int flushdir; { X char *p; X X exprerr = 0; X (void) signal(SIGFPE, eval_fpe); X if (setjmp(fpe_save)) { X error ("Floating point exception in cell %s", v_name(v->row, v->col)); X p = ""; X } else { X p = seval(se); X } X (void) signal(SIGFPE, quit); X if (exprerr) { X efree((struct ent *)0, se); X return; X } X if (constant(se)) { X label(v, p, flushdir); X if (p) X xfree(p); X efree((struct ent *)0, se); X if (v->flags & is_strexpr) { X efree (v, v->expr); X v->expr = (struct enode *)0; X v->flags &= ~is_strexpr; X } X return; X } X efree (v, v->expr); X v->expr = se; X v->flags |= (is_changed|is_strexpr); X if (flushdir<0) v->flags |= is_leftflush; X else v->flags &= ~is_leftflush; X #ifdef EXPRTREE X totoptree(); #endif X FullUpdate++; X changed++; X modflg++; } X #ifdef EXPRTREE /* X * put an expression in the expression tree, only the top of each branch is X * in the firstev list X */ totoptree(v) struct ent *v; { X int right; X int left; X if (!v->expr) X return; X #ifdef notdef X right = FALSE; X left = FALSE; X switch(v->expr->op) X { X /* no real expression */ X case 'v': X if (v->expr->o.v->evnext) X evdel(v->expr->o.v); X case 'k': X case LMAX: X case LMIN: X case NOW: X case O_SCONST: X case O_VAR: X default: X return; X X /* left && right */ X case '#': X case '%': X case '&': X case '*': X case '+': X case '-': X case '/': X case '<': X case '=': X case '>': X case '?': X case '^': X case '|': X case ATAN2: X case DTS: X case EQS: X case EXT: X case FMT: X case FV: X case HYPOT: X case IF: X case NVAL: X case PMT: X case POW: X case PV: X case REDUCE | '*': X case REDUCE | '+': X case REDUCE | 'a': X case REDUCE | 'c': X case REDUCE | 's': X case REDUCE | MAX: X case REDUCE | MIN: X case ROUND: X case STINDEX: X case SUBSTR: X case SVAL: X case TTS: X left = right = TRUE; X break; X /* right only */ X case 'f': X case 'm': X case '~': X case ABS: X case ACOS: X case ASIN: X case ATAN: X case CEIL: X case COS: X case DATE: X case DAY: X case DTR: X case EXP: X case FABS: X case FLOOR: X case HLOOKUP: X case HOUR: X case IF: X case INDEX: X case LOG10: X case LOG: X case LOOKUP: X case MINUTE: X case MONTH: X case RND: X case RTD: X case SECOND: X case SIN: X case SQRT: X case STON: X case TAN: X case VLOOKUP: X case YEAR: X right = TRUE; X break; X } X /* for now insert at the beginning of the list */ X v->evnext = firstev; X v->evprev = (struct ent *)0; X if (firstev) X firstev->evprev = v; X firstev = v; #endif X firstev = v; } #endif /* EXPRTREE*/ X void hide_row(arg) int arg; { X if (arg < 0) { X error("Invalid Range"); X return; X } X if (arg >= maxrows-1) X { X if (!growtbl(GROWROW, arg+1, 0)) X { error("You can't hide the last row"); X return; X } X } X FullUpdate++; X row_hidden[arg] = 1; } X void hide_col(arg) int arg; { X if (arg < 0) { X error("Invalid Range"); X return; X } X if (arg >= maxcols-1) X { if ((arg >= ABSMAXCOLS-1) || !growtbl(GROWCOL, 0, arg+1)) X { error("You can't hide the last col"); X return; X } X } X FullUpdate++; X col_hidden[arg] = 1; } X void clearent (v) struct ent *v; { X if (!v) X return; X label(v,"",-1); X v->v = (double)0; X if (v->expr) X efree(v, v->expr); X v->expr = (struct enode *)0; X v->flags |= (is_changed); X v->flags &= ~(is_valid); X changed++; X modflg++; } X /* X * Say if an expression is a constant (return 1) or not. X */ int constant (e) X register struct enode *e; { X return ((e == (struct enode *)0) X || ((e -> op) == O_CONST) X || ((e -> op) == O_SCONST) X || (((e -> op) != O_VAR) X && (((e -> op) & REDUCE) != REDUCE) X && constant (e -> e.o.left) X && constant (e -> e.o.right) X && (e -> op != EXT) /* functions look like constants but aren't */ X && (e -> op != NVAL) X && (e -> op != SVAL) X && (e -> op != NOW))); } X void efree (v, e) struct ent *v; struct enode *e; { X if (e) { X if (e->op != O_VAR && e->op !=O_CONST && e->op != O_SCONST X && (e->op & REDUCE) != REDUCE) { X efree(v, e->e.o.left); X efree(v, e->e.o.right); X } X if (e->op == O_SCONST && e->e.s) X xfree(e->e.s); X xfree ((char *)e); X #ifdef EXPRTREE X /* delete this cell from the eval list */ X if (v) X { if (v->evprev) X v->evprev->evnext = v->evnext; X if (v->evnext) X v->evnext->evprev = v->evprev; X } #endif /* EXPRTREE */ X } } X void label (v, s, flushdir) register struct ent *v; register char *s; int flushdir; { X if (v) { X if (flushdir==0 && v->flags&is_valid) { X register struct ent *tv; X if (v->col>0 && ((tv=lookat(v->row,v->col-1))->flags&is_valid)==0) X v = tv, flushdir = 1; X else if (((tv=lookat (v->row,v->col+1))->flags&is_valid)==0) X v = tv, flushdir = -1; X else flushdir = -1; X } X if (v->label) xfree((char *)(v->label)); X if (s && s[0]) { X v->label = xmalloc ((unsigned)(strlen(s)+1)); X (void) strcpy (v->label, s); X } else X v->label = (char *)0; X if (flushdir<0) v->flags |= is_leftflush; X else v->flags &= ~is_leftflush; X FullUpdate++; X modflg++; X } } X void decodev (v) struct ent_ptr v; { X register struct range *r; X X if (!v.vp) (void)sprintf (line+linelim,"VAR?"); X else if ((r = find_range((char *)0, 0, v.vp, v.vp)) && !r->r_is_range) X (void)sprintf(line+linelim, "%s", r->r_name); X else X (void)sprintf (line+linelim, "%s%s%s%d", X v.vf & FIX_COL ? "$" : "", X coltoa(v.vp->col), X v.vf & FIX_ROW ? "$" : "", X v.vp->row); X linelim += strlen (line+linelim); } X char * coltoa(col) int col; { X static char rname[3]; X register char *p = rname; X X if (col > 25) { X *p++ = col/26 + 'A' - 1; X col %= 26; X } X *p++ = col+'A'; X *p = '\0'; X return(rname); } X /* X * To make list elements come out in the same order X * they were entered, we must do a depth-first eval X * of the ELIST tree X */ static void decompile_list(p) struct enode *p; { X if (!p) return; X decompile_list(p->e.o.left); /* depth first */ X decompile(p->e.o.right, 0); X line[linelim++] = ','; } X void decompile(e, priority) register struct enode *e; int priority; { X register char *s; X if (e) { X int mypriority; X switch (e->op) { X default: mypriority = 99; break; X case '?': mypriority = 1; break; X case ':': mypriority = 2; break; X case '|': mypriority = 3; break; X case '&': mypriority = 4; break; X case '<': case '=': case '>': mypriority = 6; break; X case '+': case '-': case '#': mypriority = 8; break; X case '*': case '/': case '%': mypriority = 10; break; X case '^': mypriority = 12; break; X } X if (mypriority<priority) line[linelim++] = '('; X switch (e->op) { X case 'f': for (s="fixed "; line[linelim++] = *s++;); X linelim--; X decompile (e->e.o.right, 30); X break; X case 'm': line[linelim++] = '-'; X decompile (e->e.o.right, 30); X break; X case '~': line[linelim++] = '~'; X decompile (e->e.o.right, 30); X break; X case 'v': decodev (e->e.v); X break; X case 'k': (void)sprintf (line+linelim,"%.15g",e->e.k); X linelim += strlen (line+linelim); X break; X case '$': (void)sprintf (line+linelim, "\"%s\"", e->e.s); X linelim += strlen(line+linelim); X break; X X case REDUCE | '+': range_arg( "@sum(", e); break; X case REDUCE | '*': range_arg( "@prod(", e); break; X case REDUCE | 'a': range_arg( "@avg(", e); break; X case REDUCE | 'c': range_arg( "@count(", e); break; X case REDUCE | 's': range_arg( "@stddev(", e); break; X case REDUCE | MAX: range_arg( "@max(", e); break; X case REDUCE | MIN: range_arg( "@min(", e); break; X X case ABS: one_arg( "@abs(", e); break; X case ACOS: one_arg( "@acos(", e); break; X case ASIN: one_arg( "@asin(", e); break; X case ATAN: one_arg( "@atan(", e); break; X case ATAN2: two_arg( "@atan2(", e); break; X case CEIL: one_arg( "@ceil(", e); break; X case COS: one_arg( "@cos(", e); break; X case EXP: one_arg( "@exp(", e); break; X case FABS: one_arg( "@fabs(", e); break; X case FLOOR: one_arg( "@floor(", e); break; X case HYPOT: two_arg( "@hypot(", e); break; X case LOG: one_arg( "@ln(", e); break; X case LOG10: one_arg( "@log(", e); break; X case POW: two_arg( "@pow(", e); break; X case SIN: one_arg( "@sin(", e); break; X case SQRT: one_arg( "@sqrt(", e); break; X case TAN: one_arg( "@tan(", e); break; X case DTR: one_arg( "@dtr(", e); break; X case RTD: one_arg( "@rtd(", e); break; X case RND: one_arg( "@rnd(", e); break; X case ROUND: two_arg( "@round(", e); break; X case HOUR: one_arg( "@hour(", e); break; X case MINUTE: one_arg( "@minute(", e); break; X case SECOND: one_arg( "@second(", e); break; X case MONTH: one_arg( "@month(", e); break; X case DAY: one_arg( "@day(", e); break; X case YEAR: one_arg( "@year(", e); break; X case DATE: one_arg( "@date(", e); break; X case DTS: three_arg( "@dts(", e); break; X case TTS: three_arg( "@tts(", e); break; X case STON: one_arg( "@ston(", e); break; X case FMT: two_arg( "@fmt(", e); break; X case EQS: two_arg( "@eqs(", e); break; X case NOW: for ( s = "@now"; line[linelim++] = *s++;); X linelim--; X break; X case LMAX: list_arg("@max(", e); break; X case LMIN: list_arg("@min(", e); break; X case FV: three_arg("@fv(", e); break; X case PV: three_arg("@pv(", e); break; X case PMT: three_arg("@pmt(", e); break; X case NVAL: two_arg("@nval(", e); break; X case SVAL: two_arg("@sval(", e); break; X case EXT: two_arg("@ext(", e); break; X case SUBSTR: three_arg("@substr(", e); break; X case STINDEX: index_arg("@stindex(", e); break; X case INDEX: index_arg("@index(", e); break; X case LOOKUP: index_arg("@lookup(", e); break; X case HLOOKUP: two_arg_index("@hlookup(", e); break; X case VLOOKUP: two_arg_index("@vlookup(", e); break; X case IF: three_arg("@if(", e); break; X default: decompile (e->e.o.left, mypriority); X line[linelim++] = e->op; X decompile (e->e.o.right, mypriority+1); X break; X } X if (mypriority<priority) line[linelim++] = ')'; X } else line[linelim++] = '?'; } X void index_arg(s, e) char *s; struct enode *e; { X for (; line[linelim++] = *s++;); X linelim--; X decompile( e-> e.o.left, 0 ); X range_arg(", ", e->e.o.right); } X void two_arg_index(s, e) char *s; struct enode *e; { X for (; line[linelim++] = *s++;); X linelim--; X decompile( e->e.o.left->e.o.left, 0 ); X range_arg(",", e->e.o.right); X linelim--; X line[linelim++] = ','; X decompile( e->e.o.left->e.o.right, 0 ); X line[linelim++] = ')'; } X void list_arg(s, e) char *s; struct enode *e; { X for (; line[linelim++] = *s++;); X linelim--; X X decompile (e->e.o.right, 0); X line[linelim++] = ','; X decompile_list(e->e.o.left); X line[linelim - 1] = ')'; } X void one_arg(s, e) char *s; struct enode *e; { X for (; line[linelim++] = *s++;); X linelim--; X decompile (e->e.o.right, 0); X line[linelim++] = ')'; } X void two_arg(s,e) char *s; struct enode *e; { X for (; line[linelim++] = *s++;); X linelim--; X decompile (e->e.o.left, 0); X line[linelim++] = ','; X decompile (e->e.o.right, 0); X line[linelim++] = ')'; } X void three_arg(s,e) char *s; struct enode *e; { X for (; line[linelim++] = *s++;); X linelim--; X decompile (e->e.o.left, 0); X line[linelim++] = ','; X decompile (e->e.o.right->e.o.left, 0); X line[linelim++] = ','; X decompile (e->e.o.right->e.o.right, 0); X line[linelim++] = ')'; } X void range_arg(s,e) char *s; struct enode *e; { X struct range *r; X X for (; line[linelim++] = *s++;); X linelim--; X if ((r = find_range((char *)0, 0, e->e.r.left.vp, X e->e.r.right.vp)) && r->r_is_range) { X (void)sprintf(line+linelim, "%s", r->r_name); X linelim += strlen(line+linelim); X } else { X decodev (e->e.r.left); X line[linelim++] = ':'; X decodev (e->e.r.right); X } X line[linelim++] = ')'; } X void editv (row, col) int row, col; { X register struct ent *p; X X p = lookat (row, col); X (void)sprintf (line, "let %s = ", v_name(row, col)); X linelim = strlen(line); X if (p->flags & is_strexpr || p->expr == 0) { X (void)sprintf (line+linelim, "%.15g", p->v); X linelim += strlen (line+linelim); X } else { X editexp(row,col); X } } X void editexp(row,col) int row, col; { X register struct ent *p; X X p = lookat (row, col); X decompile (p->expr, 0); X line[linelim] = '\0'; } X void edits (row, col) int row, col; { X register struct ent *p; X X p = lookat (row, col); X (void)sprintf (line, "%sstring %s = ", X ((p->flags&is_leftflush) ? "left" : "right"), X v_name(row, col)); X linelim = strlen(line); X if (p->flags & is_strexpr && p->expr) { X editexp(row, col); X } else if (p->label) { X (void)sprintf (line+linelim, "\"%s\"", p->label); X linelim += strlen (line+linelim); X } else { X (void)sprintf (line+linelim, "\""); X linelim += 1; X } } X #ifdef AMIGA /* X * Author: S.J.Raybould (sie@fulcrum.bt.co.uk) X * X * Date: Thu 18th October 1990 X * X * I would have made this a macro but fn2_eval() takes a pointer X * to a function as an argument, so it must be a function. X */ double hypot(x, y) double x, y; { X return sqrt(x*x+y*y); } #endif SHAR_EOF chmod 0666 interp.c || echo 'restore of interp.c failed' Wc_c="`wc -c < 'interp.c'`" test 48439 -eq "$Wc_c" || echo 'interp.c: original size 48439, current size' "$Wc_c" rm -f _shar_wnt_.tmp fi # ============= lex.c ============== if test -f 'lex.c' -a X"$1" != X"-c"; then echo 'x - skipping lex.c (File already exists)' rm -f _shar_wnt_.tmp else > _shar_wnt_.tmp echo 'x - extracting lex.c (Text)' sed 's/^X//' << 'SHAR_EOF' > 'lex.c' && /* SC A Spreadsheet Calculator SHAR_EOF true || echo 'restore of lex.c failed' fi echo 'End of part 3' echo 'File lex.c is continued in part 4' echo 4 > _shar_seq_.tmp exit 0 -- Simon J Raybould (sie@fulcrum.bt.co.uk) // {o.o} \X/AMIGA \-/ =========================================================================== British Telecom Fulcrum, Fordrough Lane, Birmingham, B9 5LD, ENGLAND.