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 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
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
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
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).
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.
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
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.
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
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
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
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!