Skip to content

Latest commit

 

History

History
184 lines (131 loc) · 7.48 KB

File metadata and controls

184 lines (131 loc) · 7.48 KB

The host

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 this dialect

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.


Checked against the manual

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.

Not checked — taken from knowledge of the dialect

Stated plainly because the project's rule is to implement against the manual, and these were not:

  • the full operator precedence order
  • a false IF abandoning the whole rest of the line, not just the next statement
  • FOR testing 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.

Genuinely uncertain

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.


Why the interpreter is a generator

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:

  • INPUT stops a running program in the middle of a statement, waits for a human to type a line, and resumes with all state intact.
  • BREAK interrupts 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.


The acceptance test

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.