Unoriginal code alert. This is pretty much an Algol translation of Arthur J. O'Dwyer's C version, with all the faults [mine, not AJO'D's!] that such translations are likely to have. I'm distinctly out of sympathy with the problem, so there has been little testing, and there are probably several bugs. This code is less than half the size of the C, but still quite large compared with Forth/Python. If anything ever persuades me to look at this problem afresh, I'll see what can be done .... -- ANW
BEGIN
[] CHAR uc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
lc = "abcdefghijklmnopqrstuvwxyz",
digit = "0123456789",
space = " "; # tab, space #
INT ncols = UPB uc, nrows = 99;
MODE CELL = UNION (REAL, LONG INT, STRING);
# Cf AJO'D's code, no EVAL/FORMULA -- distinguished by start of
string. In a better world, a formula, in particular, would be
a data structure reflecting the operands/-ators. #
CELL empty = "<empty>";
OP ISEMPTY = (CELL c) BOOL: ( c | (STRING s): s = "<empty>" | FALSE );
PRIO PAD = 4, ISIN = 4;
OP PAD = (STRING s, INT l) STRING:
( UPB s <= l | s + (l - UPB s)*" " | s[:l-4] + " ..."),
ISIN = (CHAR c, STRING s) BOOL: char in string (c, LOC INT, s);
PROC display = (CELL c, INT len) STRING:
CASE c
IN (REAL r): float (r, 9, 3, 2), # nicer: "float (r, 0, 3, 2))"? #
(LONG INT i): whole (i, 0),
(STRING s): s
ESAC PAD len;
PROC cell name = (INT col, row) STRING: uc[col] + whole (row, 0);
PROC view col = (INT col, row) VOID:
FOR i FROM (row < 4 | 1 | row-2) TO (row > nrows-4 | nrows | row+3)
DO dump cell (col, i) OD;
PROC view row = (INT col, row) VOID:
( print (cell name (col, row) + ": ");
FOR i FROM (col < 4 | 1 | col-2) TO (col > ncols-4 | ncols | col+3)
DO print (display (get cell (i, row), 12)) OD;
print (newline) );
PROC get long int = (STRING s, REF INT j) LONG INT: # no error checks! #
IF UPB s <= 0 THEN 0
ELIF s[1] = "-" THEN j +:= 1; - get long int (s[2:], j)
ELSE INT p; LONG INT n := 0;
FOR i TO UPB s WHILE char in string (s[i], p, digit)
DO j +:= 1; n := LONG 10 * n + LENG (p-1) OD;
n
FI;
PROC get real = (STRING s, REF INT j) REAL: # none here either ... #
IF UPB s <= 0 THEN 0
ELIF s[1] = "-" THEN j +:= 1; - get real (s[2:], j)
ELSE INT p; REAL x := 0, pre := 10, post := 1;
FOR i TO UPB s WHILE char in string (s[i], p, digit + ".")
DO j +:= 1;
( s[i] = "." | pre := 1; post := 0.1 # so 123.456.789 = ??? #
|: x := pre * x + post * (p-1); pre < 5 | post /:= 10 )
OD;
x # no exponent part allowed! #
FI;
PROC input = (CHAR select, STRING line) CELL:
( select ISIN digit | get long int (line, LOC INT := 0)
|: select ISIN """=*" | line # STRING/FORMULA/EVAL #
|: select = "~" | get real (line[2:], LOC INT := 0)
| print (("Bad input! Eek!", newline));
empty );
# Evaluation of formulas.
We support the following operations on integers and reals:
+ addition
- subtraction
* multiplication
/ division
^ exponentiation
Any operation for which one operand is a real, produces a real.
Division and exponentiation always produce reals.
We support the following operations on strings:
+ concatenation
* repetition
One of the operands to the repetition operator * must be an integer
or a real; the other one must be a string.
Cells are noted by [column][row] notation, as: A1, G42.
Any cell containing a formula will have that formula evaluated;
formulas are not treated as literal strings. That is, if the
cell A1 contains the formula =A2, and A2 contains ~3.14, then
evaluating a cell with formula =A1 will yield the real ~3.14.
Only integers are allowed as literal constants: 42, 16384.
All operations are infix, with the following precedence:
high (^) (*,/) (+,-) low
Braces () can be used to change the order of operations in an
expression. #
PRIO POW = 8, OVER = 7, TIMES = 7, PLUS = 6, MINUS = 6, ATOM = 5;
OP EVAL = (STRING formula) CELL:
( INT idx := 1;
CELL c = (formula + "%") PLUS idx; # "%" acts as sentinel #
idx > UPB formula | c | empty ),
PLUS = (STRING expr, REF INT idx) CELL:
( CELL v1 := expr TIMES idx;
WHILE expr[idx] ISIN "+-"
DO CHAR op = expr[idx];
CELL v2 = expr TIMES (idx +:= 1);
(ISEMPTY v2 | v1 := v2; GOTO out);
v1 := (op = "+" | v1 PLUS v2 | v1 MINUS v2)
OD;
out: v1),
TIMES = (STRING expr, REF INT idx) CELL:
( CELL v1 := expr POW idx;
WHILE expr[idx] ISIN "*/"
DO CHAR op = expr[idx];
CELL v2 = expr POW (idx +:= 1);
(ISEMPTY v2 | v1 := v2; GOTO out);
v1 := (op = "*" | v1 TIMES v2 | v1 OVER v2)
OD;
out: v1),
POW = (STRING expr, REF INT idx) CELL:
( CELL v1 := expr ATOM idx;
ISEMPTY v1 OR expr[idx] /= "^" | v1
|: CELL v2 = expr ATOM (idx +:= 1);
ISEMPTY v2 | v2
| v1 POW v2 ),
ATOM = (STRING expr, REF INT idx) CELL:
IF CHAR next = expr[idx];
INT pos;
char in string (next, pos, digit + "-")
THEN # We have a literal integer. #
get long int (expr[idx:], idx)
ELIF next = "~" # literal real #
THEN idx +:= 1; get real (expr[idx:], idx)
ELIF char in string (next, pos, lc)
THEN # We have a cell reference. #
INT col = pos;
INT row := 0;
WHILE char in string (expr[idx +:= 1], pos, digit)
DO row := 10*row + pos - 1 OD;
CELL c := get cell (col, row);
(ISEMPTY c | c |
CASE c IN (STRING s): # FORMULA/EVAL ? #
( s[1] ISIN "=*" | EVAL s[2:] | c )
OUT c
ESAC)
ELIF expr[idx] = "("
THEN CELL v := expr PLUS (idx +:= 1);
(expr[idx] /= ")" | empty | idx +:= 1; v)
ELIF expr[idx] = "$"
THEN CELL v = expr ATOM (idx +:= 1);
(ISEMPTY v | empty |
CASE v
IN (LONG INT i): whole (i, 0),
(REAL x): float (x, 0, 5, 2)
OUT v
ESAC )
ELSE empty # What is this? It's not a formula! #
FI,
SLR = (LONG INT i) REAL: SHORTEN LONG REAL (i),
# cover up lack of ops between long ints and [unlong] reals #
POW = (CELL v1, v2) CELL: f (v1, v2, 1),
OVER = (CELL v1, v2) CELL: f (v1, v2, 2),
TIMES = (CELL v1, v2) CELL: f (v1, v2, 3),
PLUS = (CELL v1, v2) CELL: f (v1, v2, 4),
MINUS = (CELL v1, v2) CELL: f (v1, v2, 5);
PROC f = (CELL v1, v2, INT n) CELL:
CASE v1
IN (LONG INT i):
CASE v2
IN (LONG INT j):
(n | i ^ SHORTEN j, SLR i / SLR j, i * j, i + j, i - j),
(REAL y):
(n | exp (y * ln (SLR i)), SLR i / y, SLR i * y, SLR i + y,
SLR i - y),
(STRING t): ( n = 3 | SHORTEN i * t | empty )
ESAC,
(REAL x):
CASE v2
IN (LONG INT j):
(n | x ^ SHORTEN j, x / SLR j, x * SLR j, x + SLR j, x - SLR j),
(REAL y): (n | exp (y * ln (x)), x / y, x * y, x + y, x - y),
(STRING t): ( n = 3 | ENTIER x * t | empty )
ESAC,
(STRING s):
CASE v2
IN (STRING t): ( n = 4 | s + t | empty )
OUT ( n = 3 | f ( v2, v1, 3) | empty )
ESAC
ESAC;
[ncols, nrows] CELL spreadsheet;
FOR i TO ncols DO FOR j TO nrows DO spreadsheet[i, j] := empty OD OD;
PROC get cell = (INT col, row) CELL:
IF col >= 1 AND col <= ncols AND row >= 1 AND row <= nrows
THEN spreadsheet[col, row]
ELSE empty
FI;
PROC put cell = (INT col, row, CELL c) VOID:
IF col >= 1 AND col <= ncols AND row >= 1 AND row <= nrows
THEN spreadsheet[col, row] := c
FI;
PROC dump cell = (INT col, row) VOID:
print ((cell name (col, row), ": ",
display (get cell (col, row), 70), newline));
PROC fill cell = (INT col, row, CHAR c, STRING s) VOID:
( CELL t = input (c, s); NOT ISEMPTY t | put cell (col, row, t) );
PROC run = VOID:
BEGIN INT col := 1, row := 1, q;
STRING line;
FILE aux := standin;
show help (FALSE);
on logical file end (aux, (REF FILE file) BOOL: GOTO out);
DO CHAR command := ":";
IF get (aux, (line, newline));
INT p := 1;
TO UPB line WHILE line[p] ISIN space DO p +:= 1 OD;
UPB line < p
THEN show help (TRUE)
ELIF line[p] = "@"
THEN GOTO out
ELIF IF INT c;
char in string (line[p], c, uc+lc) # cell reference #
THEN INT newcol = (c > ncols | c - ncols | c);
INT newrow := 0;
WHILE ( (p +:= 1) > UPB line
| FALSE
| char in string (line[p], c, digit) )
DO newrow := 10*newrow + c-1 OD;
( 1 <= newrow AND newrow <= nrows
| col := newcol; row := newrow
| print (("Invalid cell reference ",
cell name (newcol, newrow), newline));
p := UPB line + 1; # skip rest of line #
show help (FALSE))
FI;
TO UPB line - p + 1 WHILE line[p] ISIN space DO p +:= 1 OD;
UPB line < p
THEN command := " " # User entered a reference, or nothing. #
ELIF # We have a command now. #
command := line [p];
char in string (command, q, "=*-|:?$.!~""" + digit)
THEN CASE q
IN
# = # ( p < UPB line
| fill cell (col, row, command, line[p:]) ),
# * # (p < UPB line
| fill cell (col, row, command, line[p:]);
command := "="
| CELL c = get cell (col, row);
print ((cell name (col, row), ": ",
display (( c | (STRING s):
(s[1] = "=" | EVAL s[2:] | c) | c ), 70),
newline))),
# - # view row (col, row),
# | # view col (col, row),
# : # print (("active: ", cell name (col, row), newline)),
# ? # ( get cell (col, row) | (STRING s):
( INT p;
char in string (s[1], p, "=*")
| put cell (col, row, "*="[p] + s[2:]) ) ),
# $ # ( get cell (col, row) | (STRING s):
put cell (col, row,
( s[1] ISIN "=*" | """" | "=") + s[2:]) ),
# . # # Bind: evaluate this cell permanently. #
( get cell (col, row) | (STRING s):
(s[1] ISIN "=*" | put cell (col, row, EVAL s[2:])) ),
# ! # put cell (col, row, empty)
# ~"dig # OUT fill cell (col, row, command, line[p:])
ESAC
ELSE # Just treat the input like a string. #
fill cell (col, row, """", """" + line[p:])
FI;
( command ISIN "*-|:" | SKIP | dump cell (col, row) )
OD;
out: SKIP
END;
PROC show help = (BOOL verbose) VOID:
print (( verbose |
("Use the following commands to enter data into the spreadsheet:", newline,
" A1 make A1 the currently active cell", newline,
" 1234 enter an integer into the named cell", newline,
" ~3.14 enter a real number", newline,
" hello enter a string", newline,
" ""~314 enter a string beginning with a command character", newline,
" =A2^4+3 enter a FORMULA to be displayed as a string", newline,
" *A2^4+3 enter an EVAL formula to be displayed evaluated", newline,
"Use the following commands to manipulate the spreadsheet:", newline,
" : view the name of the currently active cell", newline,
" = view the contents of the active cell", newline,
" - view the contents of the active row", newline,
" | view the contents of the active column", newline,
" * evaluate the contents of the active cell", newline,
" ? convert the active cell between FORMULA and EVAL", newline,
" $ convert the active cell between FORMULA and STRING", newline,
" . permanently bind the value of the active cell", newline,
" ! delete the active cell", newline,
"Formulas may contain the infix operators +-*/^(),", newline,
"cell references, integer literals, and the prefix operator $,", newline,
"which converts any cell to its string representation.", newline)
|
("Ready for input.", newline,
"[cellnames] [command] [newvalue] [separator]", newline,
"G42 :=*|-?$.! ""x =x *x ~x ;", newline,
"Enter @ to quit.", newline) ));
run
END
Contributed by Andrew Walker, Andrew.Walker at nottingham.ac.uk