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!

Want more posts like this one? Take a moment to subscribe!

Enjoyed this article? Spread the word or join the ongoing discussion!