The status information was shown at the top:
- B8 showed where the position of the cursor though you normally just thought
of it as "there".
- The R showed that we were doing row-order calculation.
- The ! meant that that two arrow keys went up and down.
- The amount of memory available was added to the status in later versions.
We had to be careful to make this number match the number shown in the manual
since users didn't necessarily distinguish accidental properties from intrinsic
properties so we need to be careful about even something that seemed obvious.
- The black area showed formulas as they were typed. We also showed the typing
in the cell itself.
- The date and time at the bottom showed the version of the program -- we
didn't have a clock so this wasn't part of the actual program.
- Each row had a number. In this example it is left justified but we made
sure it was right justified for the product. We departed from common notation
by numbering the rows and using letters for the columns. This was because we
had only 63 columns and 255 rows. These numbers were chosen to limit the number
of bits we had to use. There had to be enough columns for a full year of production
planning plus some extra. The large number of arrows allowed for it to be used
for multiple sets of data such as a payroll table.
We originally planned to let people rename the rows and columns with labels
and implemented the feature. Eventually we decided that we needed to assure stable
reference names so prevented people form moving into the zeroth row or column.
Instead we implemented the ability to split the screen vertically or horizontally.
Remember that the screen was small so we couldn't fit that much on the screen
so a single split was enough.
The columns were all the same width within a window. This avoid a level of
indirection in reference the screen. This was a mistake since it would've added
insignificant overhead and the lack of what came be called variable column with
become a competitive disadvantage.
We made sure that there was a border between numbers in each column to assure
readability. If the last column didn't fit then we reformatted for what was visible
rather than clipping it as is typical in today's windowing systems. Clipping would
let the user see "100" instead of "1000".
We did allow the two windows to have different widths which gave some flexibility.
Movement in the two windows could be synchronized or they could be viewed independently.
A single cell could be displayed in the two windows.
The windows could also show different global formats including the underlying
formulas and a simple character graphics mode that showed the contents of the
cell as the corresponding number of asterisks. This was a primitive graphic of
plotting capability.
We did toy with the idea of using the split screen bitmap capability of the
Apple ][ to show a live graph at the bottom of the screen but it would've added
too much code.
The screen would automatically redisplay as values were changed though the
user could turn it off for manual recalculations when automatic calculations were
confusing. The ! would recalculate.
Note that it was always "recalculate" -- the first calculation was just
an unimportant special case.
Since displaying the spreadsheet was relatively slow we implemented scrolling
as a special case by copying text from one part of the screen to the other. It
took a few hundred bytes--a major investment in code, but we felt it was necessary
to give a good feel. Thus we were surprised that 1-2-3 didn't do this smooth scrolling.
Apparently Jon Sachs ran into some problems and it wasn't wroth delaying their
shipment for that feature.
Keyboard Usage and Interacting with VisiCalc
Before discussing keyboards, it's worth noting that back in 1979 people
viewed the keyboard as an impediment to using computers. After all, only
secretaries could type and the rest of us need to be able to talk to the
computer. Hence the decades spent on trying to get computers to understand
speech. It turns out that most people could type (at least those who used
spreadsheets) since it was a basic skill necessary for getting through
college. In fact, speech is a very problematic way to interact with a
spreadsheet. In fact, the spreadsheet itself is used as a communications
vehicle rather than speech.
The Apple ][ had a simple keyboard that only had upper case letters and only
two arrow keys. There were no interrupts nor a clock. If the user typed a character
before the keyboard input buffer was emptied it would be lost.
Electric Pencil was an early word processor for the Apple ][ and it would lose characters
if the user typed to fast. To avoid this problem in VisiCalc I polled the keyboard
in the middle of potentially long loop--keyboard checks were strewn throughout
the code.
The characters would be stored in the input buffer. Since the user would type
ahead there was the opposite danger -- overtyping or running ahead. It is normally
to press the arrow key until the cursor was in the right place. By the time the
user reached the correct cell there would be a lot of extra characters in the
input buffer. To prevent skidding we ignored these extra characters. Thus we preserved
type ahead but not too much. I doubt if any but the most geeky users were even
aware that there was an issue let alone a solution. This is the kind of design
detail that makes a program feel good even if you don't know why.
Since there were only two arrow keys we used the space bar to toggle between
vertical and horizontal motion and showed the current mode with the -- or ! in
the upper right hand corner of the screen. The use of the space bar in conjunction
with the arrow keys quickly became internalized to the point that users may not
have noticed they were toggling the arrows.
Since I've mentioned the arrow keys I'll get a little ahead to note that the
arrow keys worked very differently when entering a formula or label. If you pressed
the arrow key when you needed to point to a cell you see the position inline with
the formula and as soon as you typed the next character, such as a +-, the cell
would be committed and you could continue to edit the formula. But if you were
in an operator position and pressed the arrow, it would enter the formula into
the cell and move the focus to the new cell. Again, few users were probably aware
that these were very different function because the right thing "just happened"
at the right place.
This was part of the larger goal of giving the user the illusion of infinite
choice and freedom at very point even though only a very small number of choices
were allowed. In practice only a few choices made sense in that context. Thus
in the context of pointing to a cell, the arrows naturally pointed rather than
terminated the formula.
We used the same principle to avoid error messages. One motivation was very
simple -- error messages took up a lot of space. Instead we showed the formula
as it was interpreted. If what you typed didn't make sense it just didn't do the
wrong thing.
In order to keep this illusion we had to distinguish between cell names (A1)
and functions such as SUM. We used the "@" as a prefix for functions. That seemed
acceptable and apparently users didn't have a problem with it.
This was also one reason we didn't allow people to give the cells themselves
names. The bigger reason was that it wasn't necessary and the most proficient
users, those who would most value such a feature, seemed to be very well served
with out them. But we did consider allowing the use of labels instead of cell
names but, given the limits of the Apple ][, it never became an issue.
We also need to distinguish formulas from labels and for that we required a
formula start with a number or an operator such as +- or the special @. One could
use a " to force interpretation as a lab
There were a small number of commands in VisiCalc and we used the / as the
"command key". Remember that there were no f unction keys. The legacy of the /
lasted long after VisiCalc and people used to expect / to be the command key on
the IBM PC even for word processors. I got complaints when I implemented Lotus
express and required a function key for commands. Today the F10 has become standard
for that role.
The / itself was chosen because it seemed obvious to me and was otherwise available.
But it was also a good choice for Dan whose fingers just happened to be a little
crooked and were predisposed to reach that key.
The commands themselves were meant to mnemonic but we only showed the letters
since the full names would've been part of the unimplemented help system. The
goal was to have an interactive help systems that allowed you to see the full
names of commands and the keyboard options at any point but we estimated it would
have taken 2000 bytes to implemented an interactive help system and that was an
unaffordable luxury.
Overall though VisiCalc was designed for the casual user the proficient user
was well-rewarded by having an interface which didn't require one to move one's
hands off the keyboard or even look at the screen to see where a mouse pointer
wound up. The arrows were reliable ways to move one's position (well, as long
as one didn't scroll very far).
The Apple ][ had a reset key and in the first versions there was no way to
prevent the user from accidentally pressing reset and losing all the work. This
was simply unacceptable for a production product so we including a short command
sequence that could be typed into the Apple ][ monitor to continue VisiCalc. Since
we didn't know where VisiCalc interrupted we couldn't assume it was safe to continue
and only allowed the user to save the spreadsheet at that point.
Files and I/O
The Apple ][ handled IO view add-in boards. If you wanted to print to a printer
in slot 6 you would say PR#6. if that slot happened to contain a disk drive that
same operation, however, would boot from the drive. In order to avoid such problems
and do the right thing for each device VisiCalc had a table of signature bytes
for each board so that we could avoid doing something like rebooting by mistake.
We derived the bytes by examining the boards and, in effect, doing our own plug
and play. Thus you could print and VisiCalc would find the printer automatically.
Since we didn't want to be beholden to Apple, I had to reverse engineer the
low level I/O operations for the disk drives and implement a compatible file system.
The first beta copies had a bug -- I didn't reserve the bitmap for the file system
so after a few files the file system would get corrupted. Those people who were
careful and wrote their files onto two floppies were not spared -- both would
get corrupted at the same time.
Since we were handling the low level I/O operations we could also implement
a scheme to discourage copying the floppy. I also added an extra touch by having
VisiCalc write over itself after booting on the assumption that a user would test
the copy and, unlike the product disk, it wouldn't be protected. Unfortunately
the write protect tab was not reliable on those drives so we would also overwrite
the original copy.
The copy protection scheme did make the normal Apple ][ disk copy program fail.
Later this expertise allowed us to ship a single disk that could handle the original
floppies with 13 sectors per track and those with 16. It would even remember which
way it booted and then format new disks with the same number of sectors.
Eventually the copy protection become too much of an impediment and we dropped
it.
As I mentioned we also supported cassette drives in the initial version. When
we saved the spreadsheet we made sure the first operation allocated the entire
sheet since that could be a long operation and then read it back from the lower
right back. This technique also sped up loading from disk.
We saved the spreadsheet in a format that allowed us to use the keyboard input
processor to read the file. There were undocumented commands that allowed us to
set the initial value of a cell and control the loading. They would also work
from the keyboard.
Calculations and Formulas
At its heart, VisiCalc is about numbers. One of the early decisions we made
was to use decimal arithmetic so that the errors would be the same one that an
accountant would see using a decimal calculator. In retrospect this was a bad
decision because people turn out to not care and it made calculations much slower
than they would have been in binary.
We did want to have enough precision to handle large numbers for both scientific
calculations and in the unrealistic case it would be used to calculate the United
States budget. Of course, as it turned out, that was one of the real applications.
Since the formulas did depend on each other the order of (re)calculation made
a difference. The first idea was to follow the dependency chains but this would
have involved keeping pointers and that would take up memory. We realized that
normal spreadsheets were simple and could be calculated in either row or column
order and errors would usually become obvious right away. Later spreadsheets touted
"natural order" as a major feature but for the Apple ][ I think we made the right
tradeoff.
The functions or, as they were called, the @functions each had a story. Some,
like @sum seem simple enough but we did have to deal with ranges (as in
A1+AIU-A2). @average skipped over empty cells and @count could be used
to find the count of nonempty cells.
For @npv (net present value) we decided on a formula which was different
from that used in COBOL (a programming language). The COBOL committee was later
asked to be compatible with VisiCalc though I don't think they made the change.
One of the early applications for VisiCalc was my 1979 tax form. I created
@lookup for that purpose.
The transcendental functions like @sin were going to be a problem so
we decided to omit them in the initial version but, unfortunately, in this review
of VisiCalc, Carl Helmers praised that aspect of VisiCalc and we felt obliged
to implement them. This was a real pain because I had to find books on such functions
and how to compute them for the appropriate precision and range of values and
all this had to be done in very little space. It took a week or two but eventually
we did them. At this point Dan was available and joined in the programming.
While I could usually cobble together adequate routines to do what was needed
I found myself doing a bad job in converting numbers to external representation.
Late in coding I found some cases that didn't convert properly and produced results
such as "+-0". I patched around it by looking for those cases in the screen buffer
itself and fixing it. It worked well enough so that I could move on to other problems.
One design decision was to not do precedence in the formulas. If you typed
3+5*4 you got 32 instead of 23. We reasoned simple calculator users expected
each operation to take place as it was typed. In hindsight this was a mistake?people
expect precedence and the sequential operators on a simple calculator were not
viewed in terms of the whole calculation as written. Internally I had been
self-taught on how to do parsing of formulas (I keep trying to not type
equation since mathematicians talk about equations since they are normally
balancing them). In 1966 I was working on FFL (First Financial Language), a
story in its own right but for another essay, and had a vague sense of how to
do it but with some advice I figured out how to do a simple stack-based
implementation. For VisiCalc I tried to do a very compact version of this and
it would have been just as easy to implement precedence.
For those interested in the details of handling formulas ...
Internally the parser works by pushing operands and operands on the stack and
executing each operation when it was forced by a lower precedence operation.
Thus for 3+4*5+2 you push the 3 on the operand stack and the + on the operator
stack (at least, that's what I think?unless I actually run the code I presume
anything I write is buggy), then push the 4 and then the * and the 5. So we
have 3,4,5 and +,*. The + causes the previous * and + to execute, producing
3,20 and +. Next we do the first plus to get 23 on the stack, push the 2 and
then the end of the equation causes remaining operations to be performed. This
is left to right execution with precedence. All we do to not do precedence is
to treat the * and the + as being the same priority. ()'s are used to force
ordering.
I used two stacks in this example, if we didn't
have to deal with reordering, I could write the formula as 3,4,5,*,+,2,+. This
stack notation is known as reverse polish notation or RPN. (Polish?look up the
name of Pole who invented the notation). HP calculators handled this natively
and it was very natural once you got used to it. In fact, many of us liked it
better than the standard notation since one didn't have to keep all the
context in mind and there is no need for ()'s. But it wasn't a sufficient
improvement to get over the unfamiliarity.
Programming Decisions
This section is for geeks so I won't try to translate all of the terms.
OK, so what's going on behind the curtain? Faced with a 16KB target, that included
enough space to actually hold a useful spreadsheet, I went into severe design
mode. I normally don't worry about the size of my code since it takes a lot of
work and normally doesn't make a difference and there is a real risk of getting
locked into premature design decisions.
For VisiCalc I had no choice. It was made more difficult by not knowing much
about the program since no one had used it yet. Dan's ability to work on the prototype
gave us some clues about where we were headed. I started to mock up the program
by writing the initializations code, SSINIT (SpreadSheet Init) so that we had
a framework for displaying the sheet. Now all I had to do was fill in the stuff
underneath.
One guiding principle was to always have functioning code. It was the scaffolding
and all I needed to do was flesh it out. Or not. Since the program held together
omitting a feature was a choice and it gave us flexibility.
I was lucky in that basic architecture was viable. Well, after programming
for 15 years I did have some idea of how to write such a program so it was more
than luck. In fact, I did have to do some reworking as we went along but I also
left stubs such as the reality of row and column 0 for the labels even though
we didn't allow users to move there. It allowed them to be handled normally by
most of the code.
One of the earliest issues was representation -- how do I represent the formulas
in memory (and later, on disk). I was still spending a little time at Interactive
Data at that point and designing the layout was the kind of productive doodling
I needed to stay awake in a training class.
The basic approach was to allocate memory into fixed chunks so that we wouldn't
have a problem with the kind of breakage that occurs with irregular allocation.
Deallocating a cell freed up 100% of its storage. Thus a given spreadsheet would
take up the same amount of space no matter how it was created. I presumed that
the spreadsheet would normally be compact and in the upper left (low number rows
and cells) so used a vector of rows vectors. The chunks were also called cells
so I had to be careful about terminology to avoid confusion. Internally the term
"cell" always meant storage cell. These cells were allocated from one direction
and the vectors from the other. When they collided the program reorganized the
storage. It had to do this in place since there was no room left at that point
-- after all that's why we had to do the reorganization.
The actual representation was variable length with each element prefixed by
a varying length type indicator. In order to avoid having most code parse the
formula the last by was marked $ff (or 0xff in today's representation). It turned
out that valid cell references at the edges of the sheet looked like this and
created some interesting bugs.
The program was tuned for the Apple ]['s 6502 processor. It processes 8 bits
at a time and has up to 64KB bytes. The program was tuned to this processor
- Arrays of 16 bit values were split into two 8 bit arrays so that the value
could be incremented or decremented in a single operation in a loop.
- Loops tended to go from the high to low value since this saved a byte in
each loop
The assembler had macros so that instead of directly coding to the machines
conditional instructions I could use an "aif/aelse/aendif" set in order to assure
that the structure of the code was maintained. There was a special "calret" (call/return)
operator that was used for calling a subroutine at the end of a sequence of code.
It generated an efficient jump instruction but the reader could assume that the
code continued and returned at that point instead of wondering about an unstructured
transfer.
Though there was a very strong emphasis on efficiency there was even a strong
emphasis on readability. Anything that might surprise someone reading the code
was carefully documented. My assumption was that I would forget those key points
myself. It also helped others who would read the code but the primary audience
was me in the fog of coding.
The spreadsheet array itself was designed for efficient processing.
- The spreadsheet itself had a guard row so that there was no need to constantly
check against the bounds but sometimes an operation, such as replicate would
skip over this guard and would simply wrap back. It might have produced strange
results but no damage.
- The insert operation was really a move and it fail if the last row was occupied.
The bias was towards assuming that everything was in the upper left.
- Though I knew how to constructed shared structures the formulas were copied
into each cell. Attempting to share the representation would have added complexity
and we would have had to hide this from the user in order to make irregularities
in the spreadsheet normal. Programs that emphasized regularity failed because
they did time series well and everything else poorly.
Tools and Environment
We started programming by using the tools from ECD on Multics. I worked at
night when the computer time cost $1/hour. Honeywell also took advantage of the
low fee to use the machine at night to develop the Ada language for the military
but those developers worked during their day from France.
Once we formed the company we decided to buy our own computer. Prime was trying
to follow in Multics' path and seemed like a reasonable choice. It had a PL/1
compiler which made it easy for me to port some of my work. The first project
was to implement a simple editor and then an assembler and other development tools.
On the side I evolved an editor that was originally supposed to be a line editor
(QED) into a screen editor (Emacs). Seth Steinberg who had worked at MIT Architecture
Machine Group added a lisp-like language and Emacs became a very useful tool.
We even programmed an email system within the editor. It also allowed us to create
tools to assist in formatting the code and other housekeeping.
Later we developed our own language and tools to make it easier to code and
to write for multiple platforms but that's a separate topics. For now I'm focusing
on the early days of VisiCalc. Once we had grown we used more advanced tools such
as an in-circuit emulator which allowed us to examine code as it executed. It
proved itself invaluable when I found that the reason my code was failing was
that the memory ship was defective and the values changed on their own! Sometimes
it is the hardware!
Later Enhancements Versions
The Apple ][ version was the key version of VisiCalc. Before we shipped we
added the ability to run demonstration programs and eventually evolved this into
a macro capability for advanced VisiCalc.
Before we shipped we started to grow the company and moved from my attic to
share office with John Strayhorn's Renaissance Computing and hired Steve Lawrence
who I had worked with at ECD.
After the Apple ][ we created versions for the Commodore Pet and the Atari
800 since both use the same processor. Brad Templeton (now President of EFF) helped
us with the Pet port.
We hired Seth Steinberg to convert the code to the Z80 and he did a very faithful
port. He was very skilled and recognized the goal was to keep the code base aligned
rather than trying to show off his own skills.
And then, well, that's another story.
On October 17th we finally shipped the prototype, now dubbed the production
version, of VisiCalc.