Scared myself

Su and C are out of town on a road trip with Grandpa, and I'm taking it easy. Slept in 'til 10:30 today. Yay! The weather was crappy so I stayed around the house, and got sucked into a project to the exclusion of all else.

Ages ago, someone asked about coding up the Traveller trade rules (from Merchant Prince) into a simulation, which turned into a long debate about languages and platforms. Given the desire to make the result useful to everyone, I thought it was a no-brainer to do it in JavaScript with a web interface. I even went so far as to extract the source code of the canonical implementation - "TRADER" - from an Apple II disk image and take a peek at the code. Pretty damned elegant, actually.

So earlier this week I started translating from Applesoft BASIC to JavaScript. As I went, though, I kept changing directions - for example, did I want it to be a verbatim, mechanical translation, or did I want to optimize things to take advantage of JavaScript's strengths? Did I want it to follow the procedural path of the original, or turn it into a modern object-oriented approach? And how should I attempt to integrate BASIC's notion of blocking input with a web page's modern eventing model?

The more I wrote, the more I realized the resulting implementation would look nothing like the original, which had to contend with the joys of BASIC's limited flow control, data structures and variable naming. Which really defeated the whole purpose of the project, since just reimplementing the Merchant Prince rules from scratch would probably take a quarter of the time. The only advantage of doing a translation was to get it to be an exact simulation... which would invariably fail since the code was radically restructured and differences inadvertantly introduced.


Walking back from an after-work party last night, I thought: Why don't I dive down a level deeper, and just write an Applesoft BASIC processor in JavaScript?

And that's what became of Saturday, April 21st, 2007 in Josh's life - sitting at the computer, dog at my side, music on.

At this point, it's running inside the Windows Scripting Host (command line JScript interpreter for Windows) and I've got the following fully functional:

  • CLEAR // Clear all variables
  • END // End program
  • FRE // Garbage collect
  • GOTO // GOTO linenum
  • GOSUB // GOSUB linenum
  • RETURN // Return from a GOSUB
  • POP // Change last GOSUB into a GOTO
  • FOR // FOR i = m TO m STEP s
  • NEXT // NEXT [i]
  • LET // Assign a variable, LET x = expr
  • PRINT // Output to the screen
  • INPUT // Read input from keyboard
  • IF // IF expr (THEN|GOTO) linenum|statement [statement ... ]
  • REM // Comment
  • NOTRACE // Turn off line tracing
  • TRACE // Turn on line tracing
  • Binary boolean operators: OR, AND
  • Relational operators: '<', '>', '=', '<=' /'=<', '>='/'=>', '<>'/'><'
  • Additive operators: '+', '-'
  • Multiplicative operators: '*', '/'
  • Power operators: '^'
  • Unary operators: '+', '-', NOT
  • ABS // Absolute value
  • ASC // ASCII code of a character
  • ATN // Arctangent
  • CHR$ // Character, given an ASCII code
  • COS // Cosine
  • EXP // Raise e to the specified power
  • INT // Integer portion of the value
  • LEFT$ // Left end of a string
  • LEN // Length substring
  • LOG // Natural log
  • MID$ // Arbitrary substring
  • RIGHT$ // Right substring
  • RND // Pseudorandom number
  • SGN // Sign (-1, 0, 1)
  • SIN // Sine
  • SQR // Square root
  • STR$ // Convert number to string
  • TAN // Tangent
  • VAL // Convert string to number
What's NOT supported so far:
  • I haven't thoroughly exercised string variables and expressions
  • ON expr GOTO ... is not yet implemented
  • Arrays (and DIM) are not yet implemented
  • DATA/READ/RESTORE are not yet implemented
  • User defined functions (DEF FN) are not yet implemented)
  • ONERR/RESUME are not yet implemented... and probably won't be, since this was buggy on the Apple II
  • HOME, HTAB, VTAB, GET, TAB, POS() are tricky as console APIs are not supported (easily?) by WSH
  • PRINT CHR$(4) hooks and DOS emulation for loading files.
Also, the following will probably never be supported:
  • TEXT, COLOR=, DRAW, FLASH, GR, HLIN, HPLOT, HCOLOR=, HGR, HGR2, INVERSE, NORMAL, PLOT, ROT=, SCALE=, SCRN(), VLIN, XDRAW - I'm not touching different display modes!
  • CONT, DEL, LIST, NEW, RUN, SPEED= - primarily immediate mode, not interesting for 99.99% of programs
  • CALL, HIMEM:, IN#, LOMEM:, PDL(), PEEK(), POKE, PR#, USR(), WAIT - interact with the native memory space of the Apple II/6502. Not happening.
  • LOAD, RECALL, SAVE, SHLOAD, STORE - cassette tape I/O. Yeah, I'm all over that.
Although honestly, if I can figure out console I/O, doing INVERSE, HTAB/VTAB, and PDL() would be fun - PONG!

The next big step, really, is to get it working in a web page. This is fairly straightforward:

  • I already have a basic TTY emulator. Not glass, paper. Write-only display. Line-oriented programs. Not sexy but it works.
  • The program is an array of statements, with the execution context an index into that. There is an explicit stack for statements (but not for functions). Each statement, in turn, is a  pointer into that. Each statement, in turn, is an array of tokens. To handle making INPUT blocking, I can push a stack object (like a GOSUB) that points to the middle of the INPUT statement, and then basically "yield" out of the program. Restoring should be as simple a RETURNing from a gosub.
Before I had the interpreter up and running, the routines that used the TTY would basically operate as chains of closures, passing in "after the user inputs something input, here's the point in this closure to rehydrate". This should be even simpler, and it was this realization that made me really want to pursue this.

So how did I scare myself?

Well, as I've been coding up the interpreter I've also been writing a test program in Applesoft that I run to test features as they come online or as I refactor and clean up the code - I have code which spits out the Fibbonacci sequence, a "guess my number" game and a "guess your number" game, various output demos, and so on. Well, I decided to see if the code-to-character function (CHR$) was working. So I plunked in this code:

10 PRINT CHR$(7) : END
And ran it. It gave a syntax error - it didn't think CHR$ was a valid variable name, let alone a special function. Oops. After a few minutes of puttering around in the code, I realized that the regular expression matching reserved words didn't have $ escaped. But I was curious as to why CHR$ wasn't a valid variable, and it took several more minutes to figure that out (another regular expression glitch). So I escaped it, and ran it again.

To understand what happened next, there are some key points above:

  • I've been home alone for a week
  • I've been staring at the computer all day
  • I have the music cranked on my computer
  • ASCII code point 7 is the "BELL" character
  • Between the time I wrote the test code and the time it ran successfully, almost 10 minutes had passed.
My computer emitted a very long, very sharp, high pitched "beep!" and I nearly jumped out of my skin.
But it worked!