/* * spread sheet program * commands are copy, read, write, print, blank, format, quit * labels begin with ' and are limited in length * numbers are stored fixed point (2 decimal places) in longs */ #include "stdio.h" /* sizes */ #define MAXROW 100 #define MAXCOL 25 #define MAXLINE 100 #define MAXNODE 5000 #define MAXSTR 16 /* screen locations */ #define FRAMEW 5 #define FRAMEH 1 #define CURCELL 22 #define MSG 23 /* default values for width, format, and justify */ #define DEFWID 10 #define DEFFMT 2 #define DEFJST 'l' /* screen and keyboard defines */ #define ESC 27 #define DEL 127 #define HELP 0x6200 #define UNDO 0x6100 #define INS 0x5200 #define CLR 0x4700 #define UP 0x4800 #define DOWN 0x5000 #define LEFT 0x4B00 #define RIGHT 0x4D00 #define BACKSP 8 #define LJUST 'l' #define RJUST 'r' /* cell types */ #define FREE 0 #define STRING 1 #define VALUE 2 #define CELL 3 #define ERR 4 #define ADD '+' #define SUB '-' #define MUL '*' #define DIV '/' #define NEG '_' #define SUM '@' /* cell structures */ typedef struct { int type; long a, b, c, d; } Node; typedef struct { int type; char s[MAXSTR]; } String; typedef union { Node n; String s; } Cell; Cell *cell[MAXROW][MAXCOL]; /* cell pointers */ Cell space[MAXNODE]; /* Cell space */ Cell *nextfree; /* free list of nodes */ Cell extra; /* an extra one when empty */ int freecnt; /* count of free nodes */ char linebuf[MAXLINE]; /* keyboard input buffer */ char showbuf[MAXLINE]; /* buffer for show routine */ char filename[MAXLINE]; /* load/save file name */ char prname[MAXLINE]; /* print file name */ char prwin[MAXLINE]; /* print window */ int crow, ccol; /* current cursor row and col */ int frow, lrow; /* first and last displayed row */ int fcol, lcol; /* first and last displayed col */ char width[MAXCOL]; /* width of the columns */ char format[MAXCOL]; /* format of the columns */ char justify[MAXCOL]; /* justification of the cols */ int loc[MAXCOL]; /* screen location of the cols */ char *parstr; /* string for expr parser */ char tokstr[MAXLINE]; /* string for next token */ int reframe; /* need to reframe */ int redisp; /* need to recalc the display */ main(argc, argv) char *argv[]; { int c; init(); if (argc > 1) loadss(argv[1]); while (1) { display(); switch (c = get()) { case 'b': blank(); break; case 'c': copy(); break; case 'd': delete(); break; case 'e': edit(); break; case 'f': setformat(); break; case 'g': go(); break; case 'i': insert(); break; case 'l': load(); break; case 'p': print(); break; case 'q': quit(); break; case 's': save(); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '\'': case '(': case '=': enter(c); break; case UP: case DOWN: case LEFT: case RIGHT: movecur(c); break; default: help(); break; } } } /* initialize the spreadsheet */ init() { int r, c; for (r = 0; r < MAXROW; r++) for (c = 0; c < MAXCOL; c++) cell[r][c] = NULL; crow = ccol = 0; frow = fcol = 0; reframe = redisp = 1; for (c = 0; c < MAXCOL; c++) { width[c] = DEFWID; format[c] = DEFFMT; justify[c] = DEFJST; } nextfree = NULL; freecnt = 0; for (r = 0; r < MAXNODE; r++) free(&space[r]); extra.n.type = ERR; erase(); } /* draw a frame on the screen displaying current window rows and cols */ frame() { int sz, i; lrow = frow + 20; if (lrow > MAXROW) lrow = MAXROW; sz = FRAMEW; for (lcol = fcol; lcol < MAXCOL && sz + width[lcol] < 80; lcol++) { loc[lcol] = sz; sz += width[lcol]; } move(0, FRAMEW); reverse(1); for (i = fcol; i < lcol; i++) { sprintf(linebuf, "%c..............................", 'a'+i); outstr(stdout, width[i], LJUST, linebuf); } for (i = frow; i < lrow; i++) { move(FRAMEH + i - frow, 0); clrline(); sprintf(linebuf, "%d.....", i); outstr(stdout, FRAMEW, LJUST, linebuf); } reverse(0); clrbelow(); redisp = 1; } /* refresh the contents of the current display window */ display() { int r, c, n; Cell *p; char *content(); if (reframe) frame(); if (redisp) { for (r = frow; r < lrow; r++) { for (c = fcol; c < lcol; c++) { if (p = cell[r][c]) { move(FRAMEH+r-frow, loc[c]); value(stdout, c, p); } } } } move(0, 0); /* how much free space left */ n = ((long)freecnt * 100L) / (long)MAXNODE; sprintf(linebuf, "%d%%", n); outstr(stdout, FRAMEW, LJUST, linebuf); move(CURCELL, 0); /* what is the content of current cell */ clrbelow(); printf("%c%d: %s", ccol+'a', crow, content(cell[crow][ccol], 0)); move(FRAMEH+crow-frow, loc[ccol]); /* place cursor */ reframe = redisp = 0; } /* output the value of the cell */ value(fp, c, p) FILE *fp; Cell *p; { int i, wid, fmt, jst; char *s, *ntoa(); long n, eval(); wid = width[c]; fmt = format[c]; jst = justify[c]; if (p == NULL) outstr(fp, wid, jst, ""); else if (p->s.type == STRING) outstr(fp, wid, jst, p->s.s); else { n = eval(p); outstr(fp, wid, jst, ntoa(n, fmt)); } } /* get a line from a file */ rdline(fp, bp) FILE *fp; char *bp; { int i, c; for (i = 0; (c = getc(fp)) != '\n' && c != EOF; i++) if (c >= ' ') *bp++ = c; *bp = 0; return i; } /* get a line from the keyboard */ char * getline(prompt, start) char *prompt, *start; { int i, j, n, c, plen, change; move(MSG, 0); clrline(); for (plen = 2; *prompt; plen++) put(*prompt++); ps("? "); i = 0; if (start) { while (linebuf[i] = start[i]) put(linebuf[i++]); } n = i; while ((c = get()) != '\r') { change = 0; switch (c) { case LEFT: if (i) i--; break; case RIGHT: if (i < n) i++; break; case BACKSP: if (i == 0) break; i--; case DEL: if (i == n) break; n--; for (j = i; j < n; j++) linebuf[j] = linebuf[j+1]; change++; break; case ESC: return NULL; break; case INS: c = ' '; default: if (c < ' ' || c > DEL) break; for (j = n; j > i; j--) linebuf[j] = linebuf[j-1]; n++; linebuf[i++] = c; change++; break; } if (change) { j = (i ? i-1 : 0); move(MSG, plen+j); while (j < n) put(linebuf[j++]); put(' '); } move(MSG, plen+i); } linebuf[n] = 0; return (n ? linebuf : NULL); } /* move the cursor, if move goes outside the window, adjust frame */ movecur(c) { switch (c) { case UP: if (crow) crow--; if (crow < frow) { frow--; reframe++; } break; case DOWN: if (crow < MAXROW-1) crow++; if (crow >= lrow) { frow++; reframe++; } break; case LEFT: if (ccol) ccol--; if (ccol < fcol) { fcol--; reframe++; } break; case RIGHT: if (ccol < MAXCOL-1) ccol++; if (ccol >= lcol) { fcol++; reframe++; } break; } } /* time to go */ quit() { char *s; if (s = getline("quit", "yes")) { if (*s == 'y') { move(24, 0); exit(0); } } } /* expression entry parsing */ char * next() { int c; char *s; while ((c = *parstr) && c <= ' ') parstr++; s = tokstr; if (alphanum(c)) { while (alphanum(*parstr)) *s++ = *parstr++; } else *s++ = *parstr++; *s = 0; return tokstr; } /* create a new node */ Cell * mkcell(t, a, b, c, d) long a, b, c, d; { Node *p; if ((p = nextfree) == NULL) { move(MSG, 0); printf("out of space"); return &extra; } else { freecnt--; nextfree = p->a; p->type = t; p->a = a; p->b = b; p->c = c; p->d = d; return p; } } /* parse the string (pointed at by parstr) into an expression tree */ Cell * parse() { char *s; Cell *x, *factor(), *tail(); x = factor(); s = next(); x = tail(x, *s); } /* more parsing, here we look for operators */ Cell * tail(x, op) Cell *x; { int p; char *s; Cell *y, *factor(); while (1) { if (!(p = prec(op))) return x; y = factor(); s = next(); if (prec(*s) > p) y = tail(y, *s); x = mkcell(op, x, y); op = *s; } } /* return the precedence of the given operator */ prec(op) { switch (op) { case '+': case '-': return 1; case '*': case '/': return 2; case '@': return 3; default: return 0; } } /* parse a factor of an expression */ Cell * factor() { char *s; int i, c, cr, cc; long aton(); Cell *x, *y; if (!(s = next())) return NULL; switch (c = *s) { case '\'': x = mkcell(STRING, 0L, 0L); s = x->s.s; for (i = 0; i < MAXSTR && (*s++ = *parstr++); i++) ; return x; case '-': x = factor(); return mkcell(NEG, x, 0L); case '(': return parse(); default: if (c >= 'a' && c <= 'z') { y = cc = c - 'a'; x = cr = atoi(s+1); return mkcell( check(cr, cc) ? CELL : ERR, x, y); } else if (c >= '0' && c <= '9') { x = aton(s); return mkcell(VALUE, x, 0); } else { move(MSG, 0); printf("bad factor: %s", s); return NULL; } } } /* set the value of a cell */ setvalue(r, c, x) long x; { Cell *p; if (p = cell[r][c]) free(p); cell[r][c] = x; } /* free up all space associated the given pointer */ free(p) Node *p; { switch (p->type) { case ADD: case SUB: case MUL: case DIV: case SUM: free(p->b); case NEG: free(p->a); } p->type = FREE; p->a = nextfree; nextfree = p; freecnt++; } /* get a filename from the user and load a spreadsheet */ load() { char *s; if (s = getline("load file", filename)) { loadss(s); } } /* load a spreadsheet file */ loadss(name) char *name; { FILE *fp; int c; char *s; strcpy(filename, name); if (fp = fopen(name, "r")) { init(); while (rdline(fp, parstr = linebuf)) { s = next(); switch (c = *s) { case '=': s = next(); ccol = *s - 'a'; crow = atoi(s+1); if (check(crow, ccol)) setvalue(crow, ccol, parse()); break; case 'f': if (s = next()) { c = atoi(s); if (s = next()) width[c] = atoi(s); if (s = next()) format[c] = atoi(s); if (s = next()) justify[c] = *s; } break; } } reframe = redisp = 1; chkframe(); fclose(fp); } else { printf(" can't open ", name); get(); } } /* save a spreadsheet on a file */ save() { int r, c; FILE *fp; char *name, *content(); if (name = getline("save file", filename)) { strcpy(filename, name); fp = fopen(name, "w"); for (r = 0; r < MAXROW; r++) for (c = 0; c < MAXCOL; c++) if (cell[r][c]) { fprintf(fp, "= %c%d %s\n", c+'a', r, content(cell[r][c], 0)); } for (c = 0; c < MAXCOL; c++) if (width[c] != DEFWID || format[c] != DEFFMT || justify[c] != DEFJST) fprintf(fp, "f %d %d %d %c\n", c, width[c], format[c], justify[c]); fclose(fp); } } /* print a report onto a file */ print() { char *s; FILE *fp; int r, c, trow, tcol, brow, bcol; if (!(s = getline("print window", prwin))) return; strcpy(prwin, s); if (!window(s, &trow, &tcol, &brow, &bcol)) return; if (s = getline("file name", NULL)) { strcpy(prname, s); fp = fopen(s, "w"); for (r = trow ; r <= brow; r++) { for (c = tcol; c <= bcol; c++) value(fp, c, cell[r][c]); putc('\n', fp); } fclose(fp); } } /* prompt for a cell name and move the cursor to that cell */ go() { char *p; if (p = getline("go to cell", NULL)) { if (*p >= 'a' && *p <= 'z') { ccol = *p - 'a'; crow = atoi(p+1); if (crow < 0) crow = 0; if (crow >= MAXROW) crow = MAXROW-1; if (ccol < 0) ccol = 0; if (ccol >= MAXCOL) ccol = MAXCOL-1; chkframe(); } } } chkframe() { if (crow < frow || crow >= lrow || ccol < fcol || ccol >= lcol) { frow = crow; fcol = ccol; reframe = 1; } } /* make a copy of a cell */ Cell * copyx(p, sr, sc, dr, dc) Node *p; { int r, c; Cell *x, *y; if (p == NULL) return NULL; switch (p->type) { case ADD: case SUB: case MUL: case DIV: case SUM: x = copyx(p->a, sr, sc, dr, dc); y = copyx(p->b, sr, sc, dr, dc); return mkcell(p->type, x, y); case CELL: r = p->a - sr + dr; c = p->b - sc + dc; return mkcell( check(r, c) ? CELL : ERR, (long)r, (long)c); default: return mkcell(p->type, p->a, p->b, p->c, p->d); } } /* prompt for where to copy to, and then copy it */ copy() { char *s; Cell *src; int r, c, trow, tcol, brow, bcol; if (!(s = getline("destination", NULL))) return; if (!window(s, &trow, &tcol, &brow, &bcol)) return; src = cell[crow][ccol]; for (r = trow; r <= brow; r++) for (c = tcol; c <= bcol; c++) setvalue(r, c, copyx(src, crow, ccol, r, c)); redisp = 1; } /* insert a row or a column */ insert() { char *s; if (s = getline("insert (row or col)", NULL)) { if (*s == 'r') insrow(); else if (*s == 'c') inscol(); else return; reframe = 1; } } insrow() { int r, c; Cell *p; for (r = MAXROW-2; r >= crow; r--) { for (c = 0; c < MAXCOL; c++) { p = copyx(cell[r][c], r, c, r+1, c); setvalue(r+1, c, p); } } for (c = 0; c < MAXCOL; c++) setvalue(crow, c, NULL); } inscol() { int r, c; Cell *p; for (c = MAXCOL-2; c >= ccol; c--) { width[c+1] = width[c]; format[c+1] = format[c]; justify[c+1] = justify[c]; for (r = 0; r < MAXROW; r++) { p = copyx(cell[r][c], r, c, r, c+1); setvalue(r, c+1, p); } } width[ccol] = DEFWID; format[ccol] = DEFFMT; justify[ccol] = DEFJST; for (r = 0; r < MAXROW; r++) setvalue(r, ccol, NULL); } /* delete a row or a column */ delete() { char *s; if (s = getline("delete (row or col)", NULL)) { if (*s == 'r') delrow(); else if (*s == 'c') delcol(); else return; reframe = 1; } } delrow() { int r, c; Cell *p; for (r = crow; r < MAXROW-2; r++) { for (c = 0; c < MAXCOL; c++) { p = copyx(cell[r+1][c], r+1, c, r, c); setvalue(r, c, p); } } } delcol() { int r, c; Cell *p; for (c = ccol; c < MAXCOL-2; c++) { width[c] = width[c+1]; format[c] = format[c+1]; justify[c] = justify[c+1]; for (r = 0; r < MAXROW; r++) { p = copyx(cell[r][c+1], r, c+1, r, c); setvalue(r, c, p); } } } /* enter a new expression into the cell */ enter(c) { char str[2]; if (c != '=') sprintf(str, "%c", c); else *str = 0; if (parstr = getline("enter", str)) { setvalue(crow, ccol, parse()); redisp = 1; } } /* fill the line buffer with the (unevaluated) contents of the cell */ char * content(p, cp) Cell *p; { *linebuf = 0; showx(p, cp); return linebuf; } /* recursive support routine for content() */ showx(p, cp) Node *p; { int op, np; char *ntoa(); if (p == NULL) return; switch (op = p->type) { case ERR: sprintf(showbuf, "ERR"); break; case FREE: sprintf(showbuf, "FREE"); break; case STRING: sprintf(showbuf, "'%s", ((String *)p)->s); break; case CELL: sprintf(showbuf, "%c%d", (short) (p->b + 'a'), (short) p->a); break; case VALUE: sprintf(showbuf, "%s", ntoa(p->a, 2)); break; case ADD: case SUB: case MUL: case DIV: case SUM: np = prec(op); if (np < cp) strcat(linebuf, "("); showx(p->a, np); sprintf(linebuf, "%s%c", linebuf, p->type); showx(p->b, np); if (np < cp) strcat(linebuf, ")"); *showbuf = 0; break; case NEG: strcat(linebuf, "-"); showx(p->a, cp); *showbuf = 0; break; default: sprintf(showbuf, "ERR%d", p->type); break; } strcat(linebuf, showbuf); } /* erase the current cell */ blank() { char *s; if (s = getline("blank this cell", "yes")) { if (*s == 'y') { setvalue(crow, ccol, NULL); move(FRAMEH+crow-frow, loc[ccol]); outstr(stdout, width[ccol], LJUST, ""); } } } /* edit the contents of the current cell */ edit() { char *s; s = content(cell[crow][ccol], 0); if (parstr = getline("edit", s)) { setvalue(crow, ccol, parse()); redisp = 1; } } /* prompt for new column formats and change them */ setformat() { char *s; int w; sprintf(linebuf, "%d", format[ccol]); if (s = getline("fixed decimal", linebuf)) { w = atoi(s); format[ccol] = (w > 2 ? 2 : w); redisp = 1; } sprintf(linebuf, "%d", width[ccol]); if (s = getline("column width", linebuf)) { w = atoi(s); width[ccol] = (w < 2 ? 2 : w); reframe = 1; } sprintf(linebuf, "%c", justify[ccol]); if (s = getline("left or right justify", linebuf)) { if (*s == 'l' || *s == 'r') { justify[ccol] = *s; reframe = 1; } } } /* display a help message */ help() { move(MSG, 0); printf("cell entry: type \"= expr\" or \"'label\" "); move(MSG+1, 0); printf( "commands: blank copy delete edit format goto insert load print quit save"); get(); } /* evaluate a cell entry */ long eval(p) Node *p; { int x, y; long a, b, sum(); if (p == NULL) return 0L; switch (p->type) { case FREE: case STRING: case ERR: return 0L; case VALUE: return p->a; case CELL: x = p->a; y = p->b; return eval(cell[x][y]); case SUM: return sum(p->a, p->b); break; case ADD: a = eval(p->a); b = eval(p->b); return a+b; case SUB: a = eval(p->a); b = eval(p->b); return a-b; case MUL: a = eval(p->a); b = eval(p->b); return (a*b)/100; case DIV: a = eval(p->a); b = eval(p->b); return (a*100)/b; case NEG: a = eval(p->a); return -a; } } long sum(lp, rp) Node *lp, *rp; { long n; int br, bc, er, ec, r, c; n = 0L; if (lp->type == CELL && rp->type == CELL) { br = lp->a; bc = lp->b; er = rp->a; ec = rp->b; for (r = br; r <= er; r++) for (c = bc; c <= ec; c++) n += eval(cell[r][c]); } return n; } /* parse a window definition, e.g. "a0.z99" */ window(s, tr, tc, br, bc) char *s; int *tr, *tc, *br, *bc; { *tc = *s - 'a'; *tr = atoi(s+1); while (*s && *s++ != '.') ; if (*s) { *bc = *s - 'a'; *br = atoi(s+1); } else { *bc = *tc; *br = *tr; } return check(*tr, *tc) && check(*br, *bc); } /* range check on a row and column */ check(r, c) { return r >= 0 && r < MAXROW && c >= 0 && c < MAXCOL; } /* convert a string to an integer */ atoi(s) char *s; { int n, c; if (s == NULL) return 0; for (n = 0; isdig(c = *s++); ) n = n * 10 + c - '0'; return n; } /* convert a string to a "fixed point" number (i.e. "123.45") */ long aton(s) char *s; { int c, i; long n; n = 0L; if (s == NULL) return 0L; for (n = 0; isdig(c = *s++); ) n = n * 10 + (c - '0'); if (c == '.') c = *s++; for (i = 0; i < 2; i++) { n = n * 10; if (isdig(c)) { n = n + (c - '0'); c = *s++; } } return n; } /* check the types of the characters */ isdig(c) { return c >= '0' && c <= '9'; } alphanum(c) { if (c >= 'a' && c <= 'z') return 1; if (c >= 'A' && c <= 'Z') return 1; if (c == '.') return 1; return isdig(c); } /* convert the fixed point number back to a string */ char * ntoa(n, fmt) long n; { int i, neg; if (neg = n < 0) n = -n; i = 16; linebuf[--i] = 0; do { linebuf[--i] = n % 10 + '0'; n = n / 10; if (i == 13) linebuf[--i] = '.'; } while (n); while (i > 11) { if (i == 13) linebuf[--i] = '.'; else linebuf[--i] = '0'; } if (fmt == 0) linebuf[12] = 0; else if (fmt == 1) linebuf[14] = 0; if (neg) linebuf[--i] = '-'; return &linebuf[i]; } /* output a string of width n to the stream fp */ outstr(fp, wid, jst, s) FILE *fp; char *s; { int i; if (jst == RJUST) { for (i = 0; s[i]; i++) ; for ( ; i < wid; i++) putc(' ', fp); } for (i = 0; i < wid && *s; i++) putc(*s++, fp); if (jst == LJUST) { for ( ; i < wid; i++) putc(' ', fp); } } /* keyboard input and screen output/control */ get() { long c; c = trap(1, 7); if (c & 0xFF) return (short)c; /* ascii character */ else return (short)(c >> 8); /* scan code */ } move(row, col) { put(ESC); put('Y'); put(row+' '); put(col+' '); } erase() { put(ESC); put('E'); } clrline() { put(ESC); put('K'); } clrbelow() { put(ESC); put('J'); } reverse(on) { put(ESC); put(on ? 'p' : 'q'); } put(c) { trap(1, 2, c); } ps(s) char *s; { while (*s) put(*s++); }