A couple of weeks ago, I announced EndBASIC: a simple BASIC language interpreter written in Rust with a goal to provide an environment for teaching my kids how to code. That first release provided what-I-think-is a robust interpreter, but that was about it: the language features were still minimal and the interactive features were non-existent.
Well, EndBASIC 0.2.0 is here and things are changing! It’s still far from the vision I want to reach, but it’s slowly moving towards that direction. I’m a bit less satisfied about the robustness of these new features compared to those in the core language, but that’s OK: they will have to change significantly or maybe even be dropped entirely, so no harm done.
So, what’s new? Let’s take a little tour.
The REPL
The first thing is a REPL, which is where all action will take place. To start it up, simply run endbasic
and be greeted by:
$ endbasic
Welcome to EndBASIC 0.2.0.
Type HELP for interactive usage information.
Ready
⎕
If we follow the instructions and type HELP
, we can obtain interactive information about all available commands, which is a good way to see what’s available:
Ready
HELP
This is EndBASIC 0.2.0.
CLEAR Clears all variables to restore initial state.
DIR Displays the list of files on disk.
EDIT Edits the stored program.
HELP Prints interactive help.
INPUT Obtains user input from the console.
LIST Lists the contents of the stored program.
LOAD Loads the given program.
NEW Clears the stored program from memory.
PRINT Prints a message to the console.
RENUM Reassigns line numbers to make them all multiples of ten.
RUN Runs the stored program.
SAVE Saves the current program in memory to the given filename.
Type HELP followed by a command name for details on that command.
Press CTRL+D to exit.
Ready
⎕
Quite a bit of stuff! We can also type HELP
followed by any command name, like HELP EDIT
, to obtain detailed information about each command. I’m not going to bore you with that here, but one of the integration tests dumps them all for validation.
Oh, by the way: note that all input happens via rustyline so you can use a multitude of keystrokes to edit the entered command. You can even navigate history.
Anyway. Seeing a raw list of commands may still make it hard to imagine what is possible, so let’s take a look at them as groups.
Line-oriented code editor
Of these commands, the first group to highlight is a line-oriented code editor inspired by Locomotive BASIC’s interface. This is a rudimentary code editor by any standards, but it is sufficient for now (but definitely not enough for the future).
Let’s enter a program with the EDIT
command:
Ready
EDIT
10 INPUT "What's your name"; name$
20 PRINT "I see; your name is"; name$
30
Ready
⎕
Code input will stop at the first empty line. Once done, we can check that the program was indeed registered with LIST
:
Ready
LIST
10 INPUT "What's your name"; name$
20 PRINT "I see; your name is"; name$
Ready
⎕
And finally we can run our stored program with RUN
:
Ready
RUN
What's your name? Julio
I see; your name is Julio
Ready
⎕
But what if we forgot something? We can add new lines too by passing their number to EDIT
and then using RENUM
if we want tidy line numbers:
Ready
EDIT 15
15 PRINT "I forgot to say something earlier..."
Ready
LIST
10 INPUT "What's your name"; name$
15 PRINT "I forgot to say something earlier..."
20 PRINT "I see; your name is"; name$
Ready
RENUM
Ready
LIST
10 INPUT "What's your name"; name$
20 PRINT "I forgot to say something earlier..."
30 PRINT "I see; your name is"; name$
Ready
⎕
Now… I have to clarify that line numbers are meaningless here: they are just a clutch to support this rudimentary code editing experience. The language does not (and will not) implement GOTO
(which, ironically, prevents me from implementing the typical “print hello and go to 10” code sample used for teaching).
Finally, the CLEAR
and NEW
commands are slightly related here. The first lets us clear the in-memory state of the interpreter without clearing the program (that is, it wipes all variables); and the second lets us clear that same stuff plus the stored program.
Program storage
Alright, so now we know how to enter programs in the memory of the REPL. But what do we do with them once we are done? Will they be lost? No! We can use the SAVE
command to persist them to disk. Let’s create two programs:
Ready
EDIT
10 PRINT "First program"
20
Ready
SAVE "FIRST.BAS"
Ready
NEW
Ready
EDIT
10 PRINT "Second program"
20
Ready
SAVE "SECOND.BAS"
Ready
⎕
Now let’s restore them with LOAD
and execute them separately:
Ready
LOAD "FIRST.BAS"
Ready
RUN
First program
Ready
LOAD "SECOND.BAS"
Ready
RUN
Second program
Ready
⎕
And, of course, you can also look at what’s on disk with an MS-DOS inspired DIR
command:
Ready
DIR
Modified Type Size Name
2020-05-07 05:45 22 FIRST.BAS
2020-05-07 05:45 23 SECOND.BAS
2 file(s), 45 bytes
Ready
⎕
These file operations all work on a single directory that, by default, is ~/Documents/endbasic/
but can be customized with the --programs-dir
flag at startup time. You should not be able to escape that directory from within the interpreter, but of course you can use a separate code editor on the files in that directory if you so wish.
Other changes
Aside from the REPL and everything related to it, the language itself has also seen some improvements as shown in the release notes. These include support for :
as a statement delimiter, _
characters in identifiers, a better INPUT
command, and a MOD
operator.
Separately… maybe you want a scripting language for your Rust program? I’ve put some effort in this directory by making the core interpreter minimal, which should allow embedding with full control of what the executed scripts can do. Even INPUT
and PRINT
are optional. The examples subdirectory shows how you might go about this, but be aware that I make zero promises about the API for now. I should be posting on this topic soon though.
For 0.3.0, the major thing I’m planning is a full-screen command-line application and a bunch of screen manipulation commands (think CLS
, COLOR
, and LOCATE
). These will take quite a bit of fiddling to get right, especially considering that, for some reason, I want to support Windows. Crossterm looks promising in this regard though. I have no idea how I’ll go about integration testing though.
And with that, go cargo install endbasic
while it’s fresh on any macOS, Linux, or Windows system!