docs/model33.md is about the machine. This is about what is on the other
end of the wire: an Altair 8800 running Microsoft 8K BASIC, 1975.
Both halves really went together. The Altair's console was frequently an ASR-33, and Gates and Allen's BASIC printed its first output onto teletype paper.
| why not | |
|---|---|
| Dartmouth 1st edition, 1964 | no INPUT and no strings — nothing interactive can run |
| Dartmouth later editions | its substring function is SEG$, which matches no other BASIC |
| DEC BASIC-PLUS | historically apt, but the listings are much harder to obtain |
| Microsoft 8K | chosen — the 1978 games book is written in it, the listings are public domain, the manual documents it, and every home-computer BASIC since is its descendant |
web/js/basic.js is an original implementation written against the MITS
Altair BASIC Reference Manual (1975). It contains no MITS or Microsoft
code. The manual is the specification: implement against it, not against
memory.
It is not byte-equivalent to the original and does not try to be. The 8080 version had its own single-precision floating point library; this runs on JavaScript doubles, so the two will disagree at the extremes.
These are the details that decide whether a program merely runs or actually behaves correctly. Each was read out of the manual rather than recalled.
Print zones are fourteen columns.
"The comma divides the 72 character line into 5 columns, each 14 characters wide. The last two of the positions on the line are not used."
and the comma near the end of a line does not pad, it breaks:
"if the print head is at print position 56 or more, then a carriage return/line feed is executed."
TAB(I) never moves the head backwards.
"Zero is the leftmost column on the terminal, 71 the rightmost. If the carriage is beyond position I, then no printing is done."
RND(X) does three different things, and the games depend on which:
"X<0 starts a new sequence of random numbers using X. Calling RND with the same X starts the same random number sequence. X=0 gives the last random number generated. Repeated calls to RND(0) will always return the same random number. X>0 generates a new random number between 0 and 1."
Get this wrong and the programs still run and still look plausible, which is the hardest class of bug to notice.
An undimensioned array gets eleven elements.
"If A(I) is used in a program before it has been dimensioned, BASIC reserves space for 11 elements (0 through 10)."
ON ... GOTO has two different out-of-range behaviours, not one:
"If I=0 or I attempts to select a non-existent line … the statement after the ON statement is executed. However, if I is >255 or <0, an FC error message will result."
Numbers print with a leading sign column and a trailing space, six
significant digits, and .333333 rather than 0.333333.
The prompt is OK, not READY. The manual uses it throughout — "the
CLOAD will type out OK as usual".
NEXT takes a list, and a bare NEXT is legal.
"If no variable is given, matches the most recent FOR loop. … A single NEXT may be used to match multiple FOR statements. Equivalent to NEXT V:NEXT W."
The order matters: the first loop with iterations left jumps, which ends the
statement. Close them all at once and CHECKERS stops working.
DATA is not tokenized. Its items are stored as they were written and
interpreted when READ asks for them, which is why DATA 42 reads into
either A or A$ — and why MASTERMIND's colour list survives, since
otherwise the OR inside ORANGE is a keyword. The space between DATA and
an opening quote is not part of the item either: keeping it turned
DATA " ",0 into two spaces and lost BANNER its blank glyph.
There is no SAVE command. Saving to paper tape is: set NULL 3, type
LIST without pressing return, switch the punch on, punch a leader, then
press return. The punch is wired in parallel with the printer and does not
know what BASIC is. CSAVE/CLOAD do exist but they are cassette, on I/O
ports 6 and 7.
Stated plainly because the project's rule is to implement against the manual, and these were not:
- the full operator precedence order
- a false
IFabandoning the whole rest of the line, not just the next statement FORtesting the limit after incrementing- true being
-1 - the exact wording of
?REDO FROM START
They are very likely right. They have not been verified, and one similar
assumption — ON ... GOTO — turned out to be wrong when it was finally
looked up.
The magnitude at which fixed-point notation gives way to E notation.
The manual documents six significant digits and shows .333333, but not the
threshold. 1E-2 and 1E6 are used here, taken from the behaviour of later
Microsoft BASICs. A program printing very large or very small numbers should
not be trusted until this is checked against a real Altair.
This is the load-bearing design decision, and it cannot be retrofitted.
A printing terminal needs two things an ordinary run-to-completion interpreter cannot provide:
INPUTstops a running program in the middle of a statement, waits for a human to type a line, and resumes with all state intact.BREAKinterrupts a program that is not going to stop on its own.
So every statement is a generator, and output is yielded one character at a time — which happens to be exactly the granularity the paper consumes it at. The program is therefore throttled by the terminal, the way it really was, rather than computing an entire game in a millisecond and then watching the paper catch up for a minute.
{t:'out', s:ch} print this character
{t:'in'} stop; resume with gen.next(line)
{t:'tick'} still alive, nothing to print
main.js pumps the generator from the metronome in clock.js, not from the
keyboard handler.
The tick yield exists because of a specific way to hang the browser:
10 GOTO 10 produces no output, so without a periodic yield the generator
never returns control, main.js never regains the thread, and the tab locks
up with no way to press BREAK.
Ten programs from BASIC Computer Games (Ahl, 1978), chosen because they are
good at ten characters per second — one or two lines a turn, not a grid.
npm test types each listing in a line at a time, then runs it.
Completeness is a number, not a feeling: 9 of 10. HUNT THE WUMPUS is
missing because no public-domain listing has been sourced yet.
The ten are the test; they are not the library. Every listing in the
repository was screened the same way, and the 103 that load without a syntax
error and then play are shipped in web/tapes/. That wider screen is what
found NEXT V,W and the two DATA faults above — none of the ten touches
them.
STAR TREK is deliberately excluded from the test. It prints a sector grid
every turn, which takes over a minute at 10 CPS. It is in the library for
exactly that reason: unusable at the real speed is the thing being
demonstrated, not a thing to be hidden.
See web/tapes/README.md for where the listings come from and why they
may be redistributed.
The test has already paid for itself. HAMURABI found a column-tracking bug
that none of the hand-written tests did; see the end of the No auto-wrap
section in docs/model33.md.