Table of Contents
SD=8516 Stellar BASIC
Tutorial
For a complete tutorial on BASIC, please see the SD-8516 User's Guide Chapter 3: Beginning Stellar BASIC Programming.
Example BASIC program
10 LET A = 1 20 PRINT A 30 A = A + 1 40 IF A <= 10 THEN GOTO 20 50 END
Stellar BASIC will allow this even shorter form:
10 A=1 20 ?A 30 A=A+1 40 IF A<=10 GOTO 20
(? is shorthand for PRINT.)
History
Dennis Allison’s 1975 article in Dr. Dobb’s Journal was a key moment in the history of Computer Science. It contained a formal specification of “Tiny BASIC”, a BASIC that could be implemented in less than 4 KB.
Stellar BASIC started out as an attempt to implement Pao Alto Tiny BASIC (PATB). The Tiny BASIC specification is to have an IL (intermediate language). This presents an easy to implement platform upon which BASIC itself can run.
On a machine with limited ram, storing BASIC strings takes up a lot of memory. So machines like the C64 tokenized the statements on entry and executed them that way. This is a standard feature of Pao Alto Tiny Basic, but was removed for Stellar BASIC to keep things academic.
The core features of a TinyBASIC revolve around:
- Line-numbered programs
LET(often optional)PRINTINPUTIF-THENGOTOGOSUBandRETURNFOR-NEXT- Usually only access to integer variables and integer based math
- Single letter variables (ex.
A,B,Z) - No floating-point math
- Very limited strings
- No arrays
- No file I/O
- Minimal error messages
- Very limited editing commands
Notable Tiny BASIC implementations include:
- Palo Alto Tiny BASIC (Dennis Allison)
- Li-Chen Wang’s Tiny BASIC
- 6800 Tiny BASIC
- NASCOM Tiny BASIC
- Apple I BASIC (inspired by Tiny BASIC ideas)
- Micro-Soft 8080 BASIC (larger, but influenced by Tiny BASIC work)
Stellar BASIC V1.0 is based on the intermediate language specs for Pao Alto Tiny Basic. From there, various commands were added in a haphazard order – but at it's core the same logic is used as way back in the day. Rest assured, Stellar BASIC is a true, hand-assembled version of a classic Microcomputer BASIC, both easy to learn, and powerful enough to run games and business software.
How it Works
Stack-based design
Everything in PATB is stack-based. This is a core design principle. There are three stacks:
- Expression Stack - Arithmetic evaluation
- “5 + 3 * 2” is “push 5”, “push 3”, “push 2”, “multiply” (pop 2 values, push result), “add” (pop 2 values, push result).
- GOSUB Stack - Subroutine calls
- “GOSUB 1000” – push current line number, jump to 1000
- “RETURN” – pop line number, jump back
- FOR Stack - Loop context
- “FOR I=1 TO 10” – push (I, 10, 1)
- “NEXT I” – peek stack, increment I, check if done, pop if finished
Implementing PATB from Scratch
The initian plan was to implement the IL as a service library so it could be used by other languages later on too.
- Phase 1: Variable management (3 functions: get, set, clear_all)
- Expression stack (4 functions: push, pop, peek, clear)
- I/O & String Helpers (ex. IO_GETNUM)
- String comparison, number parsing (ex. STR_SKIP_SPACE)
- Phase 2: Stack Operations
- GOSUB/RETURN stack (3 functions)
- FOR/NEXT stack (4 functions)
- Phase 3: Program Line Management
- Find line by number
- Insert/delete lines
- Navigate through program (first line, next line)
- Program storage with line markers (opcode 251)
- Phase 4: IL Interpreter Core;
- Design IL bytecode table (~30-40 IL opcodes)
- Command tokenizer (major hurdles to pass – this was hard)
- IL fetch/decode/execute loop
- Expression evaluation using the stack
- Control flow (GOTO, GOSUB, IF/THEN, FOR/NEXT)
- Phase 5: BASIC Commands
- PRINT, INPUT, LET
- RUN, LIST, NEW, CLEAR
- Integrate with term.ts to highlight a “Boot-to-basic” experience.
- Phase 6: Testing & Polish
- End-to-end BASIC program tests
- Error handling and status messages
- Bug fixes
- Performance tuning
Initial Development
Up until around Phase 5 things were going fine. Oh, how naieve I was. After I finished and tested the IL, a work in itself, I began to have a lot of trouble getting the tokenizer and execution loop to work together. After weeks of tracing through the code I decided to remove the tokenizer. At that point I was able to pinpoint the last 3 or 4 core bugs and fix them. It finally worked! It's amazing, you know, to see these things being brought up from scratch.
Implementing GOTO
The basic_execution_loop always loads the next line. However, if the U flag is set (by the GOTO executor) it loads the next line pointer from PATB_PROGRAM_POINTER instead. This was normally set by NEXT_LINE, but however it is set it then calls BASIC_EXEC_LINE. This was a rather simple addition.
Implementing BREAK
A rather simple thing, I just checked for a keypress every run loop.
; Check for ESC key LDAH $00 ; Read char (non-blocking) INT $10 JZ @continue ; ZF=1 = no key CMP AL, #27 ; ESC? JNZ @continue ;; print break message and RET
Implementing INPUT
I thought INPUT was going to be complex, but it turned out very easy. I had written input routines before. The strategy was to do a simple input routine. I used a loop with blocking input (from INT 10h) and put keys in a buffer as they were typed, ending on ENTER. I just used the TTY function (from INT 10h again) and various others to handle backspace, etc. and it just worked.
I realize this may not satisfy the curiosity of some, and, things are getting a bit complex so I'm just going to write out what needed to be done here as a sort of recipe. Later commands will follow the same general idea.
One, add the keyword to the system.
- in basic1.sda:
- Add keyword_input to the keyword list in basic1.sda
- Add the “Check INPUT” code, in this case after “check REM”. I.E. before exec_line_error.
- Add the function call in the jump table (matching the new JZ @exec_line_input from above)
- in basic2.sda:
- Add the basic_exec_input function (called from basic1.sda above).
- The purpose of this function is to read and process the INPUT command from basic.
- This function eventually calls the actual read-string function in INT 05h.
- in INT 05h
- Add in an IO_INPUT which reads in the string and returns a pointer to it (BLX).
Next cross your fingers and hope it works!
Implementing RAND()
We are going to make a RAND() which is for integers since I did not add floating point support yet. The idea is RAND(num) will return a random number between 0 and NUM-1. So RAND(100) will give 0-99. Or should I do it another way?
The big issue here is going to be how do we expand parse_expression? That's the RIGHT way to do it so that it can work in immediate mode, in a PRINT, or as a variable expression.
Implementing IF
I left this for last and not because its the best but because it was hard. I had to subordinate evaluate_expression to evaluate_factor (of an expression) and then evaluate expression would evaluate the factors of the expression. I'll write up what I did later. But it was a bit painful and I would like to take a break from it.
How PATB spec became Stellar BASIC
I tried to follow the PATB specification, but there were so many little things I either had to do differently or chose to do differently that it quickly diverged into it's own thing. For example, I added LINE_FIND_REVERSE and LINE_PREV to the IL, as well as LINE_MAKE_SPACE and LINE_REMOVE_SPACE for line management. I just wanted to be complete. For my own comfort. I also write a string handling library (INT 12h) that could be used. This turned out to be a really great idea as later on it made development go a lot faster. “An ounce of preparation is worth a pound of cure,” is very true here.
Along the way I tried to work from example code and specs (such as that published in Dr. Dobb's journal). Yet I did not completely understand what I was doing because I was unfamiliar with the assembly code used in the articles. However the basic ideas were easy to draw out and after some study I had a decent IL. I took a break and wrote a number of similar helper libraries to INT 12h, such as screen services (INT 10h). All of which, again, made future work on this very much easier.
Tokenizer issues
I had so much trouble with the tokenizer I decided to ditch it. That allowed me to get the execution loop working (after a lot of trouble!) Then I was able to start adding more commands – first GOTO then INPUT. By this time I had significantly diverged from the ideas of PATB and I couldn't really point to anything in the system that was related to PATB except some of the IL function names. I decided to call it “Stellar basic”, since after all, it is from the fantasy scientific laboratory “Stellar Dynamics”.
After I finally got the execution loop working, GOTO and INPUT I started focusing on how to get strings working. Once that was all hooked up I added a RND function. After quite some debugging and fish poking I was able to create a random number guessing game in BASIC. This was a huge milestone for me, so I set BASIC aside at this point to work on other aspects of the system.
Future Goals
I plan to try and write a text adventure game in BASIC, or add graphics commands. Then write a kind of tutorial about it, in the style of one of those great old Usborne “Write your own games” books. The main driver at that point will be whatever I need to add to BASIC to write some interesting games for the system.
Just like the old days!
Example TinyBASIC Program
This is NUMGUESS, the first program written for Stellar BASIC. It demonstrates TinyBASIC as a MVP; consisting of PRINT, LET, GOTO, INPUT, RAND and IF-THEN.
This program is also listed in the Stellar BASIC Programs list.
10 PRINT ""
20 PRINT "NUMBER GUESSING GAME V1.0"
30 PRINT "BY APPLEDOG (C) 2026"
40 PRINT "INSTRUCTIONS:"
50 PRINT " GUESS THE NUMBER, AND"
60 PRINT " TYPE YOUR ANSWER BELOW!"
70 PRINT "============================"
100 LET A = 1
110 LET B = 100
120 LET C = B - A
130 LET D = RAND(C)
140 LET F = 0
200 LET F = F + 1
300 PRINT ""
310 PRINT "ROUND "F
320 PRINT "LOW: "A
330 PRINT "HIGH: "B
340 INPUT "ENTER A NUMBER: ", E
350 IF E > B THEN GOTO 1000
360 IF E < A THEN GOTO 2000
370 IF E < D THEN GOTO 3000
380 IF E > D THEN GOTO 4000
400 GOTO 5000
1000 PRINT "THAT NUMBER IS TOO LARGE."
1010 PRINT ""
1020 GOTO 300
2000 PRINT "THAT NUMBER IS TOO SMALL."
2010 PRINT ""
2020 GOTO 300
3000 PRINT "YOUR GUESS IS TOO SMALL!"
3010 LET A = E
3020 PRINT ""
3030 GOTO 200
4000 PRINT "YOUR GUESS IS TOO LARGE!"
4010 LET B = E
4020 PRINT ""
4030 GOTO 200
5000 PRINT "YOU WIN!"
5010 LET F = 101 - F
5020 PRINT "SCORE: "F
