Brandy Basic

Dave Daniels introduces Brandy Basic

Brandy is a portable interpreter for BBC Basic, that is to say, it allows programs written in BBC Basic to be developed and run on computers other than ones running RISC OS. Naturally, though, Brandy runs best under RISC OS. My main goals when writing the program were to make it as compatible as possible with the Acorn interpreter and to allow programs that run on the RISC OS version of the program to run unchanged on other platforms. The program emulates features of RISC OS where it is necessary to do this. The plan was not to write an extended version of BBC Basic although additions have been made to the language, nor was it my intention to give people another reason to move away from RISC OS. I think that BBC Basic is still an excellent programming tool for many tasks and an interpreter such as Brandy would be useful to many people.

It has been said that Basic is a poor language and that nobody seriously uses it. In my opinion BBC Basic has always been an excellent version of it and worth looking at. It has its faults and lacks any support for current practices such as object oriented programming. On the other hand the language is simple and easy to use. Substantial programs can be written using it with a little care. It is good for small, one-off programs or for experiments, which is perhaps its greatest strength. It can also make programming enjoyable again.

September 1998 and 'Black Thursday', the day when Acorn shut down the workstation division and cancelled Phoebe, gave me the motivation to write the program. At that time I did not think that RISC OS computers would survive for very long, and if I was forced to move to a different machine then at least I would have a BBC Basic interpreter to play with. I started on the program in September 1998 and have continued to work on it ever since. I wrote most of it under RISC OS but development was really carried out in parallel under RISC OS, NetBSD and DOS. I learnt a lot about writing platform-independent code, but experience showed that it was platform-independent as long as the computer had an ARM or X86 processor in it. Oh well... The interpreter handles probably 99% of BBC Basic features, although exactly what is supported depends on the version being used. The RISC OS one is the most complete followed by the DOS one. The NetBSD/Linux version comes bottom of the list, but that just means that it lacks graphics support as well as sound.

The origins of the program go back a long way. I have wanted to write my own Basic interpreter or a compiler for many years (since I started playing with computers at school in the 1970s, in fact). I began to develop my own Basic interpreter in the early 1990s but never finished it. It could run simple programs but that was all. On the other hand, the experienced gained was most useful when I sat down to think about Brandy.

As I have just said, my interest in interpreters (and compilers) goes back a long way and I have always found the subject fascinating. Whilst some people write their own compilers, I think that interpreters are well worth looking at as well. Compiler and interpreters work in entirely different ways to achieve the same end, that is, to run programs. Compilers take the original program, the source code, and convert it into a form that can be more readily run on the computer. Interpreters take the source code and carry out the actions of the program directly from it. They work with the original program. Compilers blindly convert the program to its new form; interpreters are entirely driven by what they encounter as they wind they way through the program. Interpreters have a greater freedom than compilers, and to my mind this is what makes them interesting. On the down side, they are slower than compilers, and an interpreted program will typically run ten times slower than its compiled equivalent. The reason for this is because the interpreter works with the program in essentially the same form as the programmer and as a consequence there is a lot of overhead involved, for example, it has to locate variables every time they are referenced and check that each line of the program is syntactically correct. Minimizing this overhead is one of the challenges of writing an interpreter. Another great advantage of interpreters is that programs can be written more quickly and debugged more easily. One-off programs can be thrown together and made to work in a short space of time. Interpreters are also easier to write than compilers!

During the 1980s I had a Grundy Newbrain and an Amstrad CPC. (Some people might remember that the Newbrain was also being considered by the BBC to be used as the BBC Micro.) Being interested by what goes on inside the machine, I investigated the Basic interpreters on both computers. The one on the Newbrain was interesting in that it was compiled as each statement was seen for the first time. Locomotive Basic on the Amstrad was interesting for other reasons, and tricks I learnt from that interpreter were important in the design of Brandy.

The program turned out to be more complex than I thought it would be. One of the design goals was to make it as compatible as possible with the Acorn interpreter but this was complicated by the need to discover BBC Basic's undocumented features: what the manual says is not always what the Acorn interpreter accepts.

One of the big problems was the program's performance. Acorn's BBC Basic interpreters have always had a reputation for speed and so making Brandy as fast as possible was another objective. The RISC OS version runs at somewhere between half and a fifth of the speed of the Acorn interpreter but I am happy with this: I do not think that there is much scope for improvement with the current program. Of course, running the interpreter on a faster processor always helps, and the program absolutely flies on a machine with a 700Mhz Pentium in it! It has been a struggle to make the program run as fast as it does on machines such as the RiscPC. My main target has been trying to reduce the overhead of interpreting the program. This has already been mentioned briefly, To expand on it, there are four sources of overhead:

The syntax of each statement has to be checked and its contents determined.

The interpreter has to search for variables and arrays in the symbol table on every reference.

Expressions have to be type checked each time they are evaluated.

The program has to be searched to find the next statement to be executed when a branch occurs, for example, when looking for the 'ELSE' part of an 'IF' statement.

To make it worse, this overhead is incurred every time a line is executed. The approach I took to reduce it was to embed pointers in the Basic program wherever possible, so that, for example, the interpreter would not have to search the symbol table for variables except the first time a statement is seen. After this it uses the pointer to reference the variable's value immediately. The same trick is used to speed up branches in the program, for example, in an 'IF' statement there is a pointer to the 'ELSE' part of the statement to avoid the overhead of finding it. This takes cares of points 2) and 4) and speeds up program execution greatly. It is the approach used by Locomotive Basic. On the other hand, points 1) and 3) cannot easily be avoided. The effects of point 1) can be reduced but you would really have to turn the interpreter into a compiler to eliminate them. I think that turning the interpreter in a semi-compiler would be an interesting step.

The program is intended to be platform-independent and to run on other types of computer with no or little change. It can be thought of as comprising of two layers, an upper layer that deals with the Basic program and a lower layer that carries out all the operating system-specific tasks such as reading a character from the keyboard or finding out the size of a file on disk. The lower layer has to accept data or present it as RISC OS would. This means that the emulation layer is trivial for RISC OS but is quite complicated in the case of, for example, DOS. Here it was necessary to write an emulation of the RISC OS VDU drivers to handle output to the screen. At times it seemed like I was trying to reimplement RISC OS! If a feature is not supported by a particular version of the program then it is up to the emulation layer to flag an error. On the other hand missing features can be implemented relatively easily and at least two people have extended the program by adding support for a number of RISC OS SWIs.

At the beginning I said that it was not my intention to write an extended version of BBC Basic. However there are two major changes. Firstly, strings can be up to 65536 characters long and, secondly, libraries can have their own private variables. There are a number of other additions, such as an extended form of the OSCLI statement (OSCLI ... TO) that allows the output from commands to be returned to the Basic program, and the FILEPATH$ pseudo-variable that supplies a list of directories to be searched when looking for libraries.. I felt that these were useful additions. There are other minor enhancements: the interpreter provides better error messages where it lists not only the number of the line in which the error occurred but also the procedure and library. plus a traceback showing the procedures and functions called at the point of the error. The TRACE options have also been modified, for example, TRACE PROC shows not only when a function or procedure is called but when it returns as well. I do not propose to add any other major extensions, although I have been considering whether labels would be worthwhile. This would allow you to write code such as:

GOSUB doit
.doit PRINT"Hello"
or
RESTORE start
.start DATA 1,2,3

The point of this would be to remove the need for line numbers.

Of course, to make the language more fashionable, I should really be adding extensions for object-oriented programming.

As the interpreter is portable some features of BBC Basic have been left out. The main one is the built-in assembler. It is not possible to call machine code routines either. Again, this is for portability reasons but also because it is messy to do from a high level language. There are also major features that are present in only some versions, or, to be precise, graphics and sound. Support for these was a major problem and only the RISC OS version fully supports them. The DOS version has graphics support but this has a number of limitations. It has no sound support. The NetBSD/Linux version lacks both. The reason for this is that there is no easy way to how to implement them. Consider graphics: under NetBSD and Linux the program could use svgalib, but there appear to be major reasons for not doing this. Alternatively the interpreter could be rewritten as an X application, but this would tie the program to X windows. On the other hand, Brandy is supplied in source form precisely so that people can add this kind of feature.

I have been working on Brandy for over two years and consider it to be largely complete. There is a list of features still to be added but none of these are major. No doubt there are still plenty of bugs to be found and fixed. It has taken a lot of effort to make it compatible with Acorn's interpreter and to run fast. I just hope you lot appreciate it!