All The Good Ones Were Taken

Z1a - The BASIC Interpreter

Of course, it wouldn't be a proper 8-bit computer without a BASIC. I considered a few "easy" options:

But in the end, I decided I'd stick with my DIY approach and write my own BASIC interpreter, which you can play with in the emulator.

Its editor and syntax are somewhat inspired by ZX Spectrum BASIC. Currently it only does 32-bit signed integer maths, can't handle arrays, can only access 47k of program memory, and probably has all kinds of bugs that I haven't tripped over yet.

But it's good enough to do the statutory Mandelbrot set, eventually.

Writing a BASIC interpreter isn't something I'd done before. I took some inspiration from vague and distant memories of how ZX Spectrum BASIC worked. One of the first things I implemented was a pair of functions to reserve or free memory at a particular address, adjusting the system variables accordingly. Then I wrote code to display a program listing. Then I added a line editor. Then I added a simple command parser that identifies the keyword and uses a jump table to process it.

Expression evaluation was the biggest challenge, since I didn't know how best to achieve it in Z80 assembler. I've used a type of recursive descent parser, which at some future point I'll attempt to explain in more detail.

It turned out to be robust enough to calculate the likes of

PRINT %101010%%1001

where % is a prefix for a binary literal, and % is the modulo/remainder operator.

Unique (?) features

I've provided a quick reference to this home-grown dialect, but here are a few of its oddities:

Some tokens work as keywords or functions. At the start of a line, INK is a keyword to set the foreground colour. In an expression, INK evaluates to the current foreground colour.

This means you can do things like

10 PRINT "Hello world! ";: INK INK+1: GOTO 10

That works because I decided that commands which accept a limited range of parameter values should discard any unwanted high bits, rather than do bounds checking and potentially throw an error. In this case, INK 256 effectively becomes INK 0.

Similarly, graphical commands like DRAW will just go off one edge of the screen and carry on from the opposite edge rather than throw an error.

Since it's currently an integer BASIC, RND provides a random 32-bit signed integer, using xorshift.

All of which means we can also do things like

10 INK RND: DRAW RND,RND: GOTO 10

or

10 INK RND: CIRCLE RND,RND,RND: GOTO 10

Which is great if you want an easy-to-type demo.

Also, NEXT works as a function, returning false if the FOR loop is in its final iteration:

10 FOR i=1 TO 10
20 PRINT i;
30 IF NEXT i THEN PRINT ",";
40 NEXT i

There are currently two variable types: string and integer (plus FOR loop control variables, which are a special type of integer variable). A variable's type is set on assignment. While $ is a valid character in variable names, it does not dictate type, so these assignments are valid:

LET abc="This is a string!"
LET x$=42

So, there's enough of a BASIC for typing in simple programs, but the story doesn't end there...