Z1a - The BASIC Interpreter
Of course, it wouldn't be a proper 8-bit computer without a BASIC. I considered a few "easy" options:
- Port ZX Spectrum BASIC to it.
- Port CP/M so I could run one of the CP/M-compatible BASICs.
- Port the Z80 version of BBC BASIC to it (this was closed-source at the time, but it's been opened since).
- Go looking for another Z80 BASIC that I could port.
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...