"
Completing a BASIC language interpreter in 2025 This is a follow-up to my previous article Developing a BASIC language in 2025 , where I describe how I got inspired to start coding a new BASIC interpreter for the 1983 Mattel ECS add-on for Intellivision. Although my interpreter was already pretty fast and with enough statements to build games, I wasn't satisfied because it still missed one thing that the ECS BASIC implements: text strings. Only two, A$ and B$, with GET and PUT, for things like getting a name from the keyboard or showing a name. I thought about strings for four days, then I decided to code things like I know what I was doing. I added a string stack pointer bas_strptr where any created string is added. The first thing to implement was an array for the string variables ( A$ - Z$ ) each element pointing to the current string contained (or zero if none). I modified the whole of the expression parser to insert the type in the Carry flag (Clear = it is a number, Set = it is a string), then I made the first string support in the language where it detects if a string name appears (letter plus the $ sign) and reads it and copies it to a new string on the stack, returning this pointer as expression value (and of course the carry flag set) The next step was assigning string variables, it simply took the pointer and stored it into the respective string variable pointer. Of course, I was afraid that I was creating a monster because I wasn't planning for the garbage collector. Then I went full-steam ahead and put support in INPUT , PRINT , and added the concatenation of strings using the plus operator, also the functions ASC , CHR$ , LEN , LEFT$ , RIGHT$ , MID$ , INSTR , VAL , and STR$ . Now I had string support in my BASIC language for the ECS, but at some point in the execution it would fill up the stack, and crash with an "Out of Memory" error. Garbage collection It was kind of crazy having a BASIC with string support but no garbage collection. I needed a way to copy strings into the respective variable, and delete the work-in-progress strings created as expressions are evaluated. It would be easy when having a C memory management system as you only have to replace the pointer, and free the original. But any memory management comes with headers and linked lists, extra memory requirements, and slowness. Given the Intellivision CP1610 processor is already slow enough (894 khz), I decided against it. However, I noticed that temporary strings are only created inside the expression parser. So what about a double stack? One stack for strings in variables, and one stack for temporary strings. I added a secondary pointer bas_strbase (I like how it sounds like star base) At the start of each statement, bas_strbase is copied to bas_strptr (thus effectively erasing the temporary strings) A problem needed to be solved: growing bas_strbase on each string assignment. I was going to implement the most simple solution: go over the 26 string variables doing comparison and movement of pointers, and insert the new string in its place. Just as I was coding this, I noticed I had an easier solution. As I was working with 16-bit words, not all values are used. I could use a value like 0xcafe to mark a non-used space, and boom! I had an idea. When doing the assignment, delete the original string (fill it with 0xcafe words), now explore the strbase area to find a string of 0xcafe words big enough to save the new string. The better part is when there is no space for the string, I simply copy the string pointer as the new bas_strbase pointer (effectively growing the base memory area), and all words between the end of the string and the previous bas_strbase pointer (ahead in memory) are filled with 0xcafe words. Full string support with garbage collection at very small price of performance. Exactly what a CP1610 processor needs. STRING_TRASH: EQU $CAFE ; ; String assign. ; R1 = Pointer to string variable. ; R3 = New string. ; string_assign: PROC PSHR R5 MVII #STRING_TRASH,R4 ; ; Erase the used space of the stack. ; MOVR R3,R2 MVI@ R2,R0 INCR R2 ADDR R0,R2 MVI bas_strbase,R0 CMPR R0,R2 BC @@3 @@4: MVO@ R4,R2 INCR R2 CMPR R0,R2 BNC @@4 @@3: ; ; Erase the old string. ; MVI@ R1,R2 TSTR R2 BEQ @@1 MVI@ R2,R0 MVO@ R4,R2 INCR R2 TSTR R0 BEQ @@1 @@2: MVO@ R4,R2 INCR R2 DECR R0 BNE @@2 ; ; Search for space at higher-addresses. ; @@1: MVII #start_strings-1,R2 CMP bas_strbase,R2 ; All examined? BNC @@6 ; Yes, jump. @@5: CMP@ R2,R4 ; Space found? BNE @@7 ; No, keep searching. CLRR R5 @@8: INCR R5 DECR R2 CMP bas_strbase,R2 BNC @@9 CMP@ R2,R4 BEQ @@8 @@9: INCR R2 MVI@ R3,R0 INCR R0 CMPR R0,R5 ; The string fits? BNC @@7 ; ; The string fits in previous space. ; MOVR R3,R4 MOVR R2,R5 MVO@ R2,R1 ; New address. @@10: MVI@ R4,R2 MVO@ R2,R5 DECR R0 BNE @@10 PULR PC @@7: DECR R2 CMP bas_strbase,R2 BC @@5 ; ; No space available. ; @@6: MVO R3,bas_strbase ; Grow space for string variables. MVO@ R3,R1 PULR PC ENDP
Example of a parser for a text adventure game using string functions. Example of a parser for a text adventure game using string functions. Going mathematic Since my floating-point library was complete with the four operations, I had an ace under the sleeve: I already had tested sin and cos functions with it, but for some reason these had a bug. For sin(1°) the resulting value was 0.0172. These functions were ported from my Pascal compiler for transputer . As Pascal happens to have exactly the same mathematical function set as a BASIC interpreter. After a whole day examining the operation instruction-by-instruction (the jzintv debugger shines here), I discovered that I did a comparison in the wrong way and corrected it. I was so happy that I went immediately to port the remaining mathematical functions ( ATN , TAN , LOG , EXP , and derived SQR and the power-of ^ operator). There were no pitfalls along the way, except one, my BASIC has a mantissa with one extra bit of precision, and EXP(LOG(64)) returned 63.999999 Both operations use a multiplication with a constant (log does it at the end, and exp in the start). I noticed that the value was misrounded for 25 bits of mantissa, so I calculated a better constant, and et voila! EXP(LOG(64)) returned 64. Making it easier for the user A lot of BASIC interpreters in the eighties didn't supported instructions for graphics. The Commodore 64 was particularly known for requiring POKE for almost anything, unless you had the somewhat expensive Simon BASIC cartridge. However, in the Intellivision you have few graphics capabilities. In the Color Stack mode you have something called Colored Squares. This means each tile on the screen (20x12) can have four colors. This means a bloxel resolution of 40x24, and each bloxel can have one of eight colors (one being the background). I implemented PLOT with these limitations, and also added PRINT AT (for putting text at any screen position), and TIMER to measure time. One of the most difficult things was implementing the floating-point number parsing. I finally decided to approach it like parsing an integer, taking note of the number of digits parsed, and take note of the position of a period. Once it reaches the biggest number it can represent (9,999,999) then it starts ignoring any further digit (but it keeps counting them) The final calculation step is to multiply it, or divide it taking in account the period position. Also taking in account any exponent present (for example, e+1 or e-3) It wasn't so expensive in computation time. I added along a FRE(0) function to know how much space remains for writing programs.
It is the eighties
Let's suppose we are working to make this BASIC interpreter really useful for the Mattel ECS. We still need two things: cassette, and printer.
Fortunately, a lot of people at Atariage Forums have worked along the years to decipher the ECS hardware (thanks to intvnut, lathe26, and decle)
The ECS contains the hardware to interface to a cassette recorder/player at 300 bauds with FSK (Frequency-Shift Keying) of 2400/4800 hz (technically this is a modem) and it also includes a UART (Universal Asynchronous Receiver/Transmitter) patterned losely after a Motorola MC6850, but the frequency selector is separated, allowing to turn on/off a relay (cassette remote control), and to switch between two ports (the cassete and the AUX port for the printer)
Now for the cassette, I was going to use 300 bauds, this means around 30 characters per second. Do you remember your 56K modem? It was 186 times faster! I needed to optimize my BASIC as I was using token numbers above $0100, so I moved them to the area $0080-$00ff. Now all the words are only used in the lower 8 bits, and the tokenized program can be saved as bytes.
I coded the cassette routines based on code published by decle in his article ECS Text Editor written in IntyBASIC with tape support and added LOAD, SAVE, and VERIFY.
I was very happy when these cassette routines worked in emulation, and I ordered cables from Amazon for trying to record and play in my cellphone.
... continue reading