Write BASIC easier with Alex Evans Python scripts

I’ve mentioned the BASIC Utilities from Alex Evans quite a bit lately. These Python scripts can be run on any system that has Python. I have used them under Windows 11 and macOS. In addition to having scripts that will tokenize and de-tokenize BASIC programs, it has some other interesting capabilities. One is the ability to have a BASIC source file that is written in a more user-friendly way — using labels instead of line numbers, and then only having them where needed.

Consider this example of a “guess the number” game:

NUMBER = RND(100)

PRINT "I AM THINKING OF A NUMBER FROM"
PRINT "1 TO 100. CAN YOU GUESS IT?"

LOOP: INPUT "WHAT IS YOUR GUESS";GUESS

IF GUESS > NUMBER THEN PRINT "THAT IS TOO HIGH!"
IF GUESS < NUMBER THEN PRINT "THAT IS TOO LOW!"
IF GUESS = NUMBER THEN PRINT "YOU GOT IT!":END

GOTO LOOP

This code can be processed through “coco_pack.py” and it will become a tokenized program (guess.bas.pack) that runs on a CoCo. The PACK routine will do things like rename longer variables (such as “GUESS”) to valid short ones, as well as change labels (LABEL:) in to line numbers. Obviously, it also adds line numbers to everything in between, giving the programmer one less thing to worry about.

I processed this file using “coco_pack.py -t guess.bas” to have it generate a text file (-t) rather than a tokenized file. It produced “guess.bas.pack” as a text file which looked like this:

0 A=RND(100):PRINT"I AM THINKING OF A NUMBER FROM":PRINT"1 TO 100. CAN YOU GUESS IT?"
1 INPUT"WHAT IS YOUR GUESS";B:IFB>A THENPRINT"THAT IS TOO HIGH!"
2 IFB<A THENPRINT"THAT IS TOO LOW!"
3 IFB=A THENPRINT"YOU GOT IT!":END
4 GOTO1

You can see how it combined as much as possible on each line, taking three separate lines (RND, and two PRINTs) in to one. The INPUT needed to be on its own line since it will be the target of a GOTO loop. The first comparison (is guess too high) could be moved to the end of that line, but the next ones needed to be separate lines.

The result is something that looks a bit more friendly being turned in to something much more compact. Using “coco_pack guess.bas” without “-t” would have produced a tokenized file ready to load on the CoCo.

A warning about variable and label names

In BASIC, there are certain things you cannot use for variable names. You cannot have a variable named “FOR” since that is a keyword, but you can use “FO“. (Besides, if you use a variable name longer than two characters, the rest get ignored. “FOR” should be honored as “FO” and be allowed, but the BASIC parser has already found “FOR” and turned it in to a token … I think.)

Some variables work fine on Color BASIC, but cannot be used on Extended BASIC. “FN” is allowed on Color BASIC, but you cannot use “FN” as a variable under Extended/Disk BASIC because FN is a token (see DEF FN). “AS” works fine as a variable for Color or Extended BASIC, but cannot be used under Disk BASIC because “AS” is now a keyword (see the “FIELD” command).

The Alex Evans tools need to follow similar tools, since they are parsing the line looking for BASIC keywords to tokenize. If you tried to make a label called “ASK:”, as I initially did, you will find that a “GOTO ASK” does not work — it turns in to “GOTO ASx” where X is a variable it made, since it treated “AS” as a token, and then K as a variable. This is something that Alex may be able to resolve in a future update, but for right now, keep labels and long variables to things that are not conflicting with BASIC keywords.

Here’s an example of bad stuff. Notice that you can use leading tabs/spaces to help “prettify” the code if you want, but since it is still BASIC lines, everything that needs to be on the same line in BASIC will have to be on the same line of this code:

FOR=1

GOTO: PRINT "GOTO"
    GOTO GOTO

LOOP: PRINT "LOOP"
    GOTO LOOP

And here is what it tries to create:

0 FOR=1:PRINT"GOTO":GOTOGOTO
1 PRINT"LOOP":GOTO1

You can see it tried to make “FOR=1”, which will error out on the CoCo, and it did not treat “GOTO” as a label, since GOTO is a keyword.

Using LOOP: works just fine, since there is no LOOP keyword.

Just keep that in mind. This is a script that creates files, and not a full BASIC interpreter that can give you errors as you type things in. :)

A warning about line format

Since these are lines of BASIC, they have to be on the same line just as BASIC expects. You can NOT write something like:

REM * THIS WON'T WORK:
IF G > N
    PRINT "THAT IS TOO HIGH!"
ELSE IF G < N
    PRINT "THAT IS TOO LOW!"

…because that would be like doing this in BASIC:

10 REM * THIS WON'T WORK:
20 IF G > N
30 PRINT "THAT IS TOO HIGH"
40 ELSE IF G < N
50 PRINT "THAT IS TOO LOW!"

The PACK process would then combine lines and produce:

0 IFA>B
1 PRINT"THAT IS TOO HIGH!":ELSEIFA<B
2 PRINT"THAT IS TOO LOW!"

“It’s still BASIC, after all!”

Alex Evans’ BASIC UTILS change everything – part 3

Or…

An easier way

See also: part 1, part 2 and part 3.

So far, we’ve explored how BASIC programs are stored in memory, experimented with moving a program somewhere else in memory and making it run, then finally creating a stupid one line program that features over 1700 bytes of PRINT statements — an impossible (to type in) program!

But there’s certainly easier ways. And one such easier way was created by Alex Evans, though I’m not sure if his intent was to create stupid crazy long line programs or not.

Over on Alex’s Github page is a series of Python scripts that manipulate CoCo BASIC programs:

https://github.com/varmfskii/basic_utils

Alex has created routines to convert an ASCII program to a tokenized one, as well as convert tokenized BASIC program to ASCII. Very useful!

But, he also has routines to RENUMBER a program, and even PACK and UNPACK a program. It’s the PACK and UNPACK that caught my interest, once I realized what they could do.

Another interesting on is the “RE ID” script, which allows you to create a BASIC program as a text file and use long, descriptive variable names. The REID script will convert them all to short variables that actually work in BASIC. That could be fun.

Let’s look at how these are used… To make them work, you just need Python installed. Mac and Linux come with it, but for Windows you’ll need to download it. You can then run these scripts as if they were programs, though specific details for Windows are something I can’t currently add because I haven’t tried it yet.

BASIC UTILS

Converting between ASCII text file and tokenized BASIC:

  • coco_tokenize.py – Convert BASIC program in text form to tokenized form.
Usage: coco_tokenize.py [<opts>] [<iname>] [<oname>]
	-b	--basic=<dialect>	basic dialect
	-c	--cassette		tokenized cassette file
	-d	--disk			tokenized disk file (default)
	-h	--help			this help
	-i<n>	--input=<file>		input file
	-o<n>	--output=<file>		output file
  • coco_detokenize.py – Convert a tokenized BASIC program into text.
Usage: coco_detokenize.py [<opts>] [<iname>] [<oname>]
	-b	--basic=<dialect>	basic dialect
	-c	--cassette		tokenized cassette file
	-d	--disk			tokenized disk file (default)
	-h	--help			this help
	-i<n>	--input=<file>		input file
	-o<n>	--output=<file>		output file

Packing BASIC program lines together, or splitting them up:

  • coco_pack.py – Make a BASIC program take as little space as possible.
Usage: coco_pack.py [<opts>] [<iname>] [<oname>]
	-b	--basic=<dialect>	basic dialect
	-c	--cassette		tokenized cassette file
	-d	--disk			tokenized disk file (default)
	-h	--help			this help
	-i<n>	--input=<file>		input file
	-k	--token-len		line length is for tokenized form
	-m	--maxline=<num>		maximum line length
	-o<n>	--output=<file>		output file
	-t	--text			output as text file
	-x	--text-len		line length is for untokenized form
  • coco_unpack.py – Split a BASIC program up to one command per line.
Usage: coco_unpack.py [<opts>] [<iname>] [<oname>]
	-b	--basic=<dialect>	basic dialect
	-c	--cassette		tokenized cassette file
	-d	--disk			tokenized disk file (default)
	-h	--help			this help
	-i<n>	--input=<file>		input file
	-n	--no-whitespace		do not add extra whitespace
	-o<n>	--output=<file>		output file

Renumbering the program:

  • coco_renumber.py – Adjust line numbers.
Usage: coco_renumber.py [<opts>] [<iname>] [<oname>]
	-b	--basic=<dialect>	basic dialect
	-c	--cassette		tokenized cassette file
	-d	--disk			tokenized disk file (default)
	-h	--help			this help
	-i<n>	--input=<file>		input file
	-o<n>	--output=<file>		output file
	-s<n>	--start=<num>		starting line number
	-t	--text			output as text file
	-v<n>	--interval=<num>	interval between line numbers

Renaming long variables:

  • coco_reid.py – Transform variable names. This allows source code with long meaningful variable names to fit the restrictions of BASIC. This will also obfuscate existing variable names since they are replaced by A, B, C, etc.
Usage: coco_reid.py [<opts>] [<iname>] [<oname>]
	-b	--basic=<dialect>	basic dialect
	-c	--cassette		tokenized cassette file
	-d	--disk			tokenized disk file (default)
	-h	--help			this help
	-i<n>	--input=<file>		input file
	-o<n>	--output=<file>		output file
	-t	--text			output as text file

Tokenizing / Detokenizing

To test these programs, I had a copy of my ALLRAM BBS from 1983. The file I had was an ASCII text file, so I was able to convert it to a tokenized BASIC program:

% coco_tokenize.py allram.bas
% ls -l allram.bas*          
-rw-r--r--@ 1 allenh  staff  6230 Apr  3  2013 allram.bas
-rw-r--r--@ 1 allenh  staff  5125 Feb 13 21:23 allram.bas.tok

Above, you can see the original test file (allram.bas) and the tokenized version (allram.bas.tok). If I had started with a tokenized file, I could convert it to ASCII like this:

% coco_detokenize.py allram.bas.tok 
% ls -l allram.bas*                
-rw-r--r--@ 1 allenh  staff  6230 Apr  3  2013 allram.bas
-rw-r--r--@ 1 allenh  staff  5125 Feb 13 21:23 allram.bas.tok
-rw-r--r--@ 1 allenh  staff  6229 Feb 13 21:23 allram.bas.tok.txt

The original “allram.bas” had a blank line at the start of the file, but other than that, it and the tokenized-then-detokenized “allram.bas.tok.txt” should be the same.

Packing

I already tried to make my *ALLRAM* BBS somewhat compact, but I’ve learned much more since 1983. Today I’d go through it line by line and combine them as much as I could. OR, I could just use “coco_pack.py” like this:

% ./coco_pack.py allram.bas    
% ls -l allram.bas*                
-rw-r--r--@ 1 allenh  staff  6230 Apr  3  2013 allram.bas
-rw-r--r--@ 1 allenh  staff  4441 Feb 13 21:26 allram.bas.pack
-rw-r--r--@ 1 allenh  staff  5125 Feb 13 21:23 allram.bas.tok
-rw-r--r--@ 1 allenh  staff  6229 Feb 13 21:25 allram.bas.tok.txt

“coco_pack” script will work on an ASCII file and produc a tokenized file that is more compact that if you’d just used “coco_tokenize” on the original file. In this case, my BASIC program (tokenized) was 5125 bytes. The packed version was 4441 bytes!

What did it do, I wondered? I then de-tokenized this packed version so I could look at it:

% ./coco_detokenize.py allram.bas.pack
% ls -l allram.bas*                 
-rw-r--r--@ 1 allenh  staff  6230 Apr  3  2013 allram.bas
-rw-r--r--@ 1 allenh  staff  4441 Feb 13 21:26 allram.bas.pack
-rw-r--r--@ 1 allenh  staff  5497 Feb 13 21:28 allram.bas.pack.txt
-rw-r--r--@ 1 allenh  staff  5125 Feb 13 21:23 allram.bas.tok
-rw-r--r--@ 1 allenh  staff  6229 Feb 13 21:25 allram.bas.tok.txt

And you can see there is now an “allram.bas.pack.txt” de-tokenized file. This allowed me to inspect and see what the routine was doing.

The pack routine removed all comments, as well as any unnecessary spaces. It also combines lines when possible, and renumbered by 1s (starting at line 0).

Original (first few lines):

0 REM *ALL RAM* BBS System 1.0
1 REM   Shareware / (C) 1983
2 REM     By Allen Huffman
3 REM  110 Champions Dr, #811
4 REM     Lufkin, TX 75901
5 CLS:FORA=0TO8:READA$:POKE1024+A,VAL("&H"+A$):NEXTA:EXEC1024:DATAC6,1,96,BC,1F,2,7E,96,A3
10 CLEAR21000:DIMNM$(200),MS$(19,10),A$,F$,S$,T$,BR$,CL$,NM$,PS$,PW$,A,B,C,CL,LN,LV,MS,NM,KY,UC
15 CL$=CHR$(12)+CHR$(14):BR$="*==============*==============*":GOSUB555

Packed:

0 CLS:FORJ=0TO8:READI$:POKE1024+J,VAL("&H"+I$):NEXTJ:EXEC1024:DATAC6,1,96,BC,1F,2,7E,96,A3:CLEAR21000:DIMA$(200),B$(19,10),I$,H$,F$,E$,B$,C$,A$,D$,G$,J,H,A,E,I,K,F,C,G,D:C$=CHR$(12)+CHR$(14):B$="*==============*==============*":GOSUB62

You can see it removed the REMs in lines 0-4, then combined lines 5, 10 and 15 in to one long line. You can also see it changed the variables names, reducing any 2-character names to a single character.

To explain the line combining, take a look at this simple program:

10 PRINT "A"
20 PRINT "B"
30 PRINT "C"

It would pack in to this:

0 PRINT"A":PRINT"B":PRINT"C"

Not all lines can be combined. If a line ends with a GOTO/GOSUB, putting anything after it wouldn’t be executed. The same is true for IF/THEN that go to a line.

For example:

10 IF A=1 THEN 100
20 IF A=2 THEN 200
30 IF A=3 THEN 300
40 END
100 REM 100
105 PRINT "100"
110 END
200 REM 200
205 PRINT "200"
210 END
300 REM 300
305 PRINT "300"
310 END

Above, the “IF A=n” lines can’t have anything after them (without modifying the program to use ELSE), so packing produces this:

0 IFA=1THEN4
1 IFA=2THEN5
2 IFA=3THEN6
3 END
4 PRINT"100":END
5 PRINT"200":END
6 PRINT"300":END

Above, the first three IF statements were left alone, since you couldn’t put anything after the “THEN”. Pack updated any GOTO that targeted a REM line to start after the REM so the REMs could be removed. (That is something it didn’t do a week or two ago when I first started writing this. Alex has made some substantial updates to the scripts lately.)

Side Note: For this example you could do something like “IF A=1 THEN 4 ELSE IF A=2 THEN 6 ELSE IF A=3 THEN 8” on one line. However, there are other examples where if the first condition was not met you still would want the following IFs to execute. There’s really no way for a script to know the intent, so it cannot “just assume” and change code like that.

The pack script is also capable of packing lines well beyond the 249 characters you can type. Consider this program made up of 26 PRINT commands:

10 PRINT "AAAAAAAAAAAAAAAAAAAAAAAAAA"
20 PRINT "BBBBBBBBBBBBBBBBBBBBBBBBBB"
30 PRINT "CCCCCCCCCCCCCCCCCCCCCCCCCC"
40 PRINT "DDDDDDDDDDDDDDDDDDDDDDDDDD"
50 PRINT "EEEEEEEEEEEEEEEEEEEEEEEEEE"
60 PRINT "FFFFFFFFFFFFFFFFFFFFFFFFFF"
70 PRINT "GGGGGGGGGGGGGGGGGGGGGGGGGG"
80 PRINT "HHHHHHHHHHHHHHHHHHHHHHHHHH"
90 PRINT "IIIIIIIIIIIIIIIIIIIIIIIIII"
100 PRINT "JJJJJJJJJJJJJJJJJJJJJJJJJJ"
110 PRINT "KKKKKKKKKKKKKKKKKKKKKKKKKK"
120 PRINT "LLLLLLLLLLLLLLLLLLLLLLLLLL"
130 PRINT "MMMMMMMMMMMMMMMMMMMMMMMMMM"
140 PRINT "NNNNNNNNNNNNNNNNNNNNNNNNNN"
150 PRINT "OOOOOOOOOOOOOOOOOOOOOOOOOO"
160 PRINT "PPPPPPPPPPPPPPPPPPPPPPPPPP"
170 PRINT "QQQQQQQQQQQQQQQQQQQQQQQQQQ"
180 PRINT "RRRRRRRRRRRRRRRRRRRRRRRRRR"
190 PRINT "SSSSSSSSSSSSSSSSSSSSSSSSSS"
200 PRINT "TTTTTTTTTTTTTTTTTTTTTTTTTT"
210 PRINT "UUUUUUUUUUUUUUUUUUUUUUUUUU"
220 PRINT "VVVVVVVVVVVVVVVVVVVVVVVVVV"
230 PRINT "WWWWWWWWWWWWWWWWWWWWWWWWWW"
240 PRINT "XXXXXXXXXXXXXXXXXXXXXXXXXX"
250 PRINT "YYYYYYYYYYYYYYYYYYYYYYYYYY"
260 PRINT "ZZZZZZZZZZZZZZZZZZZZZZZZZZ"

…and PACK would be able to combine them all in to ONE really long line:

0 PRINT"AAAAAAAAAAAAAAAAAAAAAAAAAA":PRINT"BBBBBBBBBBBBBBBBBBBBBBBBBB":PRINT"CCCCCCCCCCCCCCCCCCCCCCCCCC":PRINT"DDDDDDDDDDDDDDDDDDDDDDDDDD":PRINT"EEEEEEEEEEEEEEEEEEEEEEEEEE":PRINT"FFFFFFFFFFFFFFFFFFFFFFFFFF":PRINT"GGGGGGGGGGGGGGGGGGGGGGGGGG":PRINT"HHHHHHHHHHHHHHHHHHHHHHHHHH":PRINT"IIIIIIIIIIIIIIIIIIIIIIIIII":PRINT"JJJJJJJJJJJJJJJJJJJJJJJJJJ":PRINT"KKKKKKKKKKKKKKKKKKKKKKKKKK":PRINT"LLLLLLLLLLLLLLLLLLLLLLLLLL":PRINT"MMMMMMMMMMMMMMMMMMMMMMMMMM":PRINT"NNNNNNNNNNNNNNNNNNNNNNNNNN":PRINT"OOOOOOOOOOOOOOOOOOOOOOOOOO":PRINT"PPPPPPPPPPPPPPPPPPPPPPPPPP":PRINT"QQQQQQQQQQQQQQQQQQQQQQQQQQ":PRINT"RRRRRRRRRRRRRRRRRRRRRRRRRR":PRINT"SSSSSSSSSSSSSSSSSSSSSSSSSS":PRINT"TTTTTTTTTTTTTTTTTTTTTTTTTT":PRINT"UUUUUUUUUUUUUUUUUUUUUUUUUU":PRINT"VVVVVVVVVVVVVVVVVVVVVVVVVV":PRINT"WWWWWWWWWWWWWWWWWWWWWWWWWW":PRINT"XXXXXXXXXXXXXXXXXXXXXXXXXX":PRINT"YYYYYYYYYYYYYYYYYYYYYYYYYY":PRINT"ZZZZZZZZZZZZZZZZZZZZZZZZZZ"

That’s 886 characters of code on one line.

Pack is awesome!

But that’s not all…

Unpacking

An interesting feature of Alex’s tools is the ability to unpack a program down to something that looks pretty … but won’t run on a real CoCo. The unpack script will break everything down to one statement per line, but doesn’t add additional line numbers. This makes code prettier and easy to edit in a text editor:

1000 'USER INPUT
1005 LINE INPUT A$
 A$=LEFT$(A$,64)
 IF UC=0 OR A$=""THEN RETURN
1010 FOR C=1 TO LEN(A$)
 CH=ASC(MID$(A$,C,1))
 IF CH>96 THEN MID$(A$,C,1)=CHR$(CH-32)
1015 IF CH=92 THEN MID$(A$,C,1)="/"
1020 NEXT C
 UC=0
 RETURN
1050 'FUNCTION BORDER
1055 CLS
 PRINT CL$BR$
 PRINT TAB((32-LEN(A$))/2)A$
 PRINT BR$
 RETURN

Then, after editing, you can use pack to turn it back in to a compact program that could be tokenized to run on the CoCo.

But that’s still not all…

Re-ID-ing

It can be frustrating making a bunch of variables make sense when you are limited to one or two character variable names. Alex has a script that takes care of that, allowing you to create “unpacked” source code with long variables like this:

10 NUMBER = RND(100)
20 PRINT "I AM THINKING OF A NUMBER FROM"
30 PRINT "1 TO 100. CAN YOU GUESS IT?"
40 INPUT "WHAT IS YOUR GUESS";GUESS
50 IF GUESS > NUMBER THEN PRINT "THAT IS TOO HIGH!" ELSE IF GUESS < NUMBER THEN PRINT "THAT IS TOO LOW!" ELSE PRINT "YOU GOT IT!":END
60 GOTO 40

Doing a “coco_pack.py program.bas“, it will pack the lines together and create short variables replacing the long ones specified in the source code:

0 A=RND(100):PRINT"I AM THINKING OF A NUMBER FROM":PRINT"1 TO 100. CAN YOU GUESS IT?"
1 INPUT"WHAT IS YOUR GUESS";B:IFB>A THENPRINT"THAT IS TOO HIGH!"ELSEIFB<A THENPRINT"THAT IS TOO LOW!"ELSEPRINT"YOU GOT IT!":END
2 GOTO1

But that’s still not all…

You don’t even have to include line numbers for anything except targets of a GOTO/GOSUB. The original program could also be written like this:

NUMBER = RND(100)

PRINT "I AM THINKING OF A NUMBER FROM"
PRINT "1 TO 100. CAN YOU GUESS IT?"

40 INPUT "WHAT IS YOUR GUESS";GUESS

IF GUESS > NUMBER THEN PRINT "THAT IS TOO HIGH!" ELSE IF GUESS < NUMBER THEN PRINT "THAT IS TOO LOW!" ELSE PRINT "YOU GOT IT!":END

GOTO 40

Pack would turn it in to the same functional program:

0 B=RND(100):PRINT"I AM THINKING OF A NUMBER FROM":PRINT"1 TO 100. CAN YOU GUESS IT?"
1 INPUT"WHAT IS YOUR GUESS";A:IFA>B THENPRINT"THAT IS TOO HIGH!"ELSEIFA<B THENPRINT"THAT IS TOO LOW!"ELSEPRINT"YOU GOT IT!":END
2 GOTO1

But that’s still not all…

You don’t even need to use line numbers. A recent update to the script allows the use of a label (word followed by a colon):

NUMBER = RND(100)

PRINT "I AM THINKING OF A NUMBER FROM"
PRINT "1 TO 100. CAN YOU GUESS IT?"

LOOP: INPUT "WHAT IS YOUR GUESS";GUESS

IF GUESS > NUMBER THEN PRINT "THAT IS TOO HIGH!" ELSE IF GUESS < NUMBER THEN PRINT "THAT IS TOO LOW!" ELSE PRINT "YOU GOT IT!":END

GOTO LOOP

And that too turns in to the same packed program. This opens up some great possibilities, such as writing subroutines without having them tied to a particular line number:

SHOWBASICSTART: PRINT PEEK(25)*256+PEEK(26)
    RETURN

SHOWBASICEND: PRINT PEEK(27)*256+PEEK(28)-1
    RETURN

Then you could paste those routines in the code, and do a “GOTO SHOWBASICSTART” to use them!

Some caveats

When writing code like this, line numbers or labels only need to be included if there is a GOTO/GOSUB/etc. that goes to them. The source still needs to follow the flow of BASIC, meaning if you are writing an IF/THEN/ELSE, that should be all on one line

IF A=1 THEN PRINT "A=1" ELSE PRINT "A<>1"

Trying to spit them up like a modern language will not work:

REM *THIS WILL NOT WORK*

INPUT VALUE

10 IF VALUE=1 THEN
    PRINT "IT IS 1"
ELSE
    PRINT "IT IS NOT 1"

The script treats each line like a BASIC line, so the above test would produce:

0 INPUTVALA:IFVALA=1THEN
1 PRINT"IT IS 1":ELSE:PRINT"IT IS NOT 1"

Close, but no cigar! You can see it treats each line like a separate set of statements, and combines them with a colon between each. (Thus, the “:ELSE:”.)

So while it’s not a way to write modern-looking code, it does allow you to use long variable names and skip line numbers except where necessary.

Side Note: Alex has been adding code to allow concatenation long lines by using a backslash at the end. This was not working for me at the time of this writing, but I expect this last section will be.

Conclusion

These scripts open up some interesting possibilities for writing BASIC without having to deal with some of the limitations of basic (short variables names, line numbers, etc.). Just being able to write a program without having to renumber all the time when adding new lines in between existing lines is worth the price alone. (And the price is free, so there’s that.)

There have also been some new options added recently that I will discuss in future articles.

Until then…

Alex Evans’ BASIC UTILS change everything – part 2

See also: part 1, part 2 and part 3.

In the first part of this series, I didn’t explain who Alex Evans is or what his BASIC UTILS are. And I may not in this part, either. Rest assured, Alex Evans fans, I will be getting to this soon eventually.

The focus right now is looking at ways to make BASIC program lines that are longer than the 249 characters we can type in to the 251-byte input buffer. One way to do that is manually.

Previously I showed a three-line BASIC program that did three PRINT commands. By PEEKing through the memory of that program, we could see how it was stored:

Now that I understand how those bytes are stored, it seems it would be easy to copy them somewhere else in memory, and adjust the BASIC pointers for “start of program” and “start of variables” to reference the new location and see if it works.

And it almost does… Here’s a program that does it:

0 ' BASCLONE.BAS
10 ' START OF PROGRAM
20 ST=PEEK(25)*256+PEEK(26)
30 ' START OF VARIABLES
40 EN=PEEK(27)*256+PEEK(28)
50 ' SIZE OF PROGRAM
60 SZ=EN-ST
70 PRINT "THIS PROGRAM IS AT:"
80 PRINT "START: &H";HEX$(ST),"END: &H";HEX$(EN)
85 'PRINT "ARYTAB: &H";HEX$(PEEK(29)*256+PEEK(30)),;
86 'PRINT "ARYEND: &H";HEX$(PEEK(31)*256+PEEK(32))
90 ' NEW START
100 NS=ST+&H1000
110 PRINT "COPYING TO &H";HEX$(NS)
120 ' CLONE TO HIGHER MEMORY
130 FOR I=0 TO SZ
140 POKE NS+I,PEEK(ST+I)
150 NEXT
160 ' SHOW NEW LOCATION
170 PRINT "PROGRAM COPIED TO:"
180 PRINT "START: &H";HEX$(NS),"END: &H";HEX$(NS+SZ)
190 END
200 PRINT "START: &H";HEX$(PEEK(25)*256+PEEK(26)),"END: &H";HEX$(PEEK(27)*256+PEEK(28))

This program will start by showing the start and end of the BASIC program, and then it will copy that memory to 4K higher in memory (current start plus &H1000). After that, it prints the new start/end locations and ENDs. After running, we’d manually do the POKEs to set locations 25/26 and 27/28 to those values, and then we could RUN 200 to see if it works.

I am using HEX so it’s easy to figure out the MSB (most significant byte) and LSB (least significant byte) of the addresses for the later POKEs. You can see that I POKE 25 and 26 to the first two and last two digits of the new “START” address, and the same with 27 and 28 and the new “END” address.

Then I RUN 200 and it prints where it thinks the program is. It works!

Sorta. If I attempt to EDIT this program or add a new line, I end up with a corrupt program. There’s clearly more that needs to be done for this to really work, but it’s a good proof-of-concept.

Rather than figure out what all I need to do to make this work, I tried using the PCLEAR command. I know it will relocate the current BASIC program and adjust variables as needed. By repeating the same steps as before, I can see the “new” program is higher in memory, then by doing a PCLEAR 4 (which is what it was already set to), it relocates the BASIC back to where it should have been. I can then add a line 81 END and RUN it to see it print the location — matching the original.

Okay, that’s cool. Probably not the correct way for it to be cool, but it does appear to work. For me. Sorta.

Creating BASIC where there was none

The real goal here is to create a BASIC program that has a line that goes well beyond the 249 character tapeable line length.

Going back to my earlier example that had PRINT”A”, we can see that the PRINT token was a value of 135 (&H87), and a quote is 34, then the character(s) to print, followed by another quote 34, then the zero at the end of the line. As a simple test, I will try to create a program that PRINTs the hex digits from 0 (&H0) to 255 (&HFF). I’ll add a semicolon at the end of each PRINT so each PRINT is on the same line. The program would look like this:

PRINT"0";:PRINT"1";: ... :PRINT"A";:PRINT"B";: ... PRINT"FF";

As a reminder a BASIC program has the format of…

  • 2 bytes – Address of next line
  • 2 bytes – Line number
  • n bytes – Tokenized line
  • 1 byte – 0 end of line marker.

To have BASIC create these bytes, I’ll first have it ask for the location in memory to begin creating the BASIC line(s).

Then I’ll use a variable that tracks the address each line starts and remember that so it can be filled in later when the address of the next line is known. I’ll then POKE out the two bytes representing the line number, and then POKE out the bytes for the tokenized line which will be the PRINT token, quote, the ASCII characters for the HEX$() of the number, another quote, then a semicolon and colon. This will repeat for all 255 hex values. Then the 0 will be stored, and after that, whatever address that is will be the start of the next line. That address will be stored back at the first two bytes of the line entry, then the program will end with two zeros marking the end of the program (no address for the next line).

I came up with this program:

0 'IMPOSSIBLE>BAS
10 CLEAR 300
20 PRINT "START: &H";HEX$(PEEK(25)*256+PEEK(26)),"END: &H";HEX$(PEEK(27)*256+PEEK(28))
30 INPUT "START ADDRESS";ST
40 ' CREATE THE IMPOSSIBLE LINE
50 ' LINE START ADDRESS
60 LS=ST
70 ' STORE LINE NUMBER (10)
80 POKE LS+2,0:POKE LS+3,10
90 ' ADDRESS TO STORE DATA
100 AD=LS+4
110 ' STORE REPEATING TOKENS:
120 ' PRINT"x";:
130 ' WHEN "x" IS A HEX NUMBER
140 FOR I=0 TO 255
150 ' BUILD DATA STRING
160 TK$=CHR$(&H87)+CHR$(34)+HEX$(I)+CHR$(34)+";:"
170 'PRINT TK$
180 FOR J=1 TO LEN(TK$)
190 POKE AD,ASC(MID$(TK$,J,1)):AD=AD+1
200 NEXT
210 NEXT
220 ' STORE FINAL TWO BYTES
230 POKE AD,0:POKE AD+1,0
240 ' STORE NEXT LINE ADDRESS
250 MS=INT(AD/256):LS=AD-(MS*256)
260 POKE LS,MS:POKE LS+1,LS
270 ' SHOW RESULTS
280 PRINT "START: &H";HEX$(ST),"END: &H";HEX$(AD+2)

Similar to the previous example, I RUN this program, then wait as it generates 256 PRINT statements on one huge line 10, and then do the POKEs for 25/26 and 27/28 shown on the screen, then a PCLEAR 4 to move this new program back to where it should be:

Once that is done, I can RUN to see it fill the screen with hex digits “0” to “FF” (more than fits on a screen), and attempting to LIST the program shows only one line, but only shows the first 249 or so bytes of it:

Checking the size of the program by doing:

PRINT (PEEK(27)*256+PEEK(28))-(PEEK(25)*256+PEEK(26))

…shows that this ONE LINE program is 1782 bytes! That’s quite the long program considering it’s only one line!

Due to the limit of the 251 byte input buffer, we cannot EDIT this line without losing everything after the 249 bytes we can type. If you EDIT and press ENTER, then LIST, you’ll see the program has been truncated.

But, it proves BASIC does indeed not care about how long a line can be.

Prove it!

By typing CSAVE”IMPOSS”,A (for tape) or SAVE”IMPOSS”,A (for disk), you can save the program out in ASCII (text). You could then transfer that file (using the toolshed decb utility or other similar program) to a PC/Mac and look at it in a text editor. This is what I see:

10 PRINT"0";:PRINT"1";:PRINT"2";:PRINT"3";:PRINT"4";:PRINT"5";:PRINT"6";:PRINT"7";:PRINT"8";:PRINT"9";:PRINT"A";:PRINT"B";:PRINT"C";:PRINT"D";:PRINT"E";:PRINT"F";:PRINT"10";:PRINT"11";:PRINT"12";:PRINT"13";:PRINT"14";:PRINT"15";:PRINT"16";:PRINT"17";:PRINT"18";:PRINT"19";:PRINT"1A";:PRINT"1B";:PRINT"1C";:PRINT"1D";:PRINT"1E";:PRINT"1F";:PRINT"20";:PRINT"21";:PRINT"22";:PRINT"23";:PRINT"24";:PRINT"25";:PRINT"26";:PRINT"27";:PRINT"28";:PRINT"29";:PRINT"2A";:PRINT"2B";:PRINT"2C";:PRINT"2D";:PRINT"2E";:PRINT"2F";:PRINT"30";:PRINT"31";:PRINT"32";:PRINT"33";:PRINT"34";:PRINT"35";:PRINT"36";:PRINT"37";:PRINT"38";:PRINT"39";:PRINT"3A";:PRINT"3B";:PRINT"3C";:PRINT"3D";:PRINT"3E";:PRINT"3F";:PRINT"40";:PRINT"41";:PRINT"42";:PRINT"43";:PRINT"44";:PRINT"45";:PRINT"46";:PRINT"47";:PRINT"48";:PRINT"49";:PRINT"4A";:PRINT"4B";:PRINT"4C";:PRINT"4D";:PRINT"4E";:PRINT"4F";:PRINT"50";:PRINT"51";:PRINT"52";:PRINT"53";:PRINT"54";:PRINT"55";:PRINT"56";:PRINT"57";:PRINT"58";:PRINT"59";:PRINT"5A";:PRINT"5B";:PRINT"5C";:PRINT"5D";:PRINT"5E";:PRINT"5F";:PRINT"60";:PRINT"61";:PRINT"62";:PRINT"63";:PRINT"64";:PRINT"65";:PRINT"66";:PRINT"67";:PRINT"68";:PRINT"69";:PRINT"6A";:PRINT"6B";:PRINT"6C";:PRINT"6D";:PRINT"6E";:PRINT"6F";:PRINT"70";:PRINT"71";:PRINT"72";:PRINT"73";:PRINT"74";:PRINT"75";:PRINT"76";:PRINT"77";:PRINT"78";:PRINT"79";:PRINT"7A";:PRINT"7B";:PRINT"7C";:PRINT"7D";:PRINT"7E";:PRINT"7F";:PRINT"80";:PRINT"81";:PRINT"82";:PRINT"83";:PRINT"84";:PRINT"85";:PRINT"86";:PRINT"87";:PRINT"88";:PRINT"89";:PRINT"8A";:PRINT"8B";:PRINT"8C";:PRINT"8D";:PRINT"8E";:PRINT"8F";:PRINT"90";:PRINT"91";:PRINT"92";:PRINT"93";:PRINT"94";:PRINT"95";:PRINT"96";:PRINT"97";:PRINT"98";:PRINT"99";:PRINT"9A";:PRINT"9B";:PRINT"9C";:PRINT"9D";:PRINT"9E";:PRINT"9F";:PRINT"A0";:PRINT"A1";:PRINT"A2";:PRINT"A3";:PRINT"A4";:PRINT"A5";:PRINT"A6";:PRINT"A7";:PRINT"A8";:PRINT"A9";:PRINT"AA";:PRINT"AB";:PRINT"AC";:PRINT"AD";:PRINT"AE";:PRINT"AF";:PRINT"B0";:PRINT"B1";:PRINT"B2";:PRINT"B3";:PRINT"B4";:PRINT"B5";:PRINT"B6";:PRINT"B7";:PRINT"B8";:PRINT"B9";:PRINT"BA";:PRINT"BB";:PRINT"BC";:PRINT"BD";:PRINT"BE";:PRINT"BF";:PRINT"C0";:PRINT"C1";:PRINT"C2";:PRINT"C3";:PRINT"C4";:PRINT"C5";:PRINT"C6";:PRINT"C7";:PRINT"C8";:PRINT"C9";:PRINT"CA";:PRINT"CB";:PRINT"CC";:PRINT"CD";:PRINT"CE";:PRINT"CF";:PRINT"D0";:PRINT"D1";:PRINT"D2";:PRINT"D3";:PRINT"D4";:PRINT"D5";:PRINT"D6";:PRINT"D7";:PRINT"D8";:PRINT"D9";:PRINT"DA";:PRINT"DB";:PRINT"DC";:PRINT"DD";:PRINT"DE";:PRINT"DF";:PRINT"E0";:PRINT"E1";:PRINT"E2";:PRINT"E3";:PRINT"E4";:PRINT"E5";:PRINT"E6";:PRINT"E7";:PRINT"E8";:PRINT"E9";:PRINT"EA";:PRINT"EB";:PRINT"EC";:PRINT"ED";:PRINT"EE";:PRINT"EF";:PRINT"F0";:PRINT"F1";:PRINT"F2";:PRINT"F3";:PRINT"F4";:PRINT"F5";:PRINT"F6";:PRINT"F7";:PRINT"F8";:PRINT"F9";:PRINT"FA";:PRINT"FB";:PRINT"FC";:PRINT"FD";:PRINT"FE";:PRINT"FF";:

Pretty cool! A one line program that is 1700+ bytes long. Sweet!

There’s got to be an easier way…

And we’ll do that in the next part.

Until then…

Alex Evans’ BASIC UTILS change everything – part 1

See also: part 1, part 2 and part 3.

Or at least a lot of things.

Or perhaps just one thing, but it’s pretty darned spiffy thing.

I’ll get to that shortly eventually. But first…

Typing in BASIC in BASIC

When it came to typing in BASIC, my Commodore VIC-20 had a full screen editor that let you cursor around the screen and type, pretty much, anywhere you wanted. You could cursor up to a command you just typed, change it, then hit enter and run it again. You could LIST a program, cursor up to a line, make a change, hit ENTER and it would be modified.

Sky-Ape-Er loader, lines -25.

This was one thing I missed when I switched from my VIC to a 64K Extended Color BASIC CoCo. Fortunately, the EDIT command in Extended BASIC ended up being much faster for me than cursoring around the screen and inserting/deleting things. It sure would have been nice to have both.

Side note: The irony that the computer with full screen editing did not have arrow keys, and the computer that had no full screen editing did have arrow keys, was not lost on me, even as a kid.

The original Color BASIC did not have the EDIT command. If you had an error or typo in a line, your only option was to retype the whole line. Since a 1980 4K CoCo had very little space for BASIC programs, and since each new line took an extra 5 bytes of overhead, I suppose many programmers had to pack lines as much as possible just to make the program fit… For those writing smaller programs, or with upgraded memory (you could get a 16K upgrade!), maybe they stuck to writing shorter lines…especially if they were used to having errors in the program.

Side note: In addition to saving program memory, packing multiple instructions on a line also sped up the program since it no longer had to scan over line numbers moving from instruction to instruction.

Line length limit

When you begin typing a line on the CoCo, everything you type is going in to a buffer in memory. The Color BASIC Unravelled disassembly book labels this buffer as LINBUF, and describes it as follows:

After line header comes LINBUF which is a 251-byte buffer to store BASIC input line as it is being typed in. This 251-byte area is also used for several different functions but primarily it is used as a line input buffer.

Color BASIC Unravelled, page F3

In the disassembly, I see that this buffer is located just a bit before the 512 bytes used by the 32-column screen.

The buffer is at &H2DC (732), followed by a 41 byte “STRING BUFFER” (whatever that is for) at &H3D7 (983) and then the &H200 (512) bytes for video at &H400 (1024).

The disassembly reserves “LBUFMX+1” bytes for this buffer, but even without looking that up, we could figure out how big the buffer is by subtracting the start of the STRBUF after it (&H3D7) from the start of the LINBUF (&H2DC). That gives us 251 bytes. And, indeed, looking up what LBUFMX is, we find it is indeed 250:

…so “LBUFMX+1” would give us the 251.

I like it when the math checks out.

This means when you go to type in a BASIC program line, you shouldn’t be able to type any more than 251 characters. And, actually, it stops you after typing in the 249th character:

Above, you can see I was able to type seven (7) full 32-character lines (224 characters) and then twenty-five (25) more characters on the final line before BASIC stopped me. 224 + 25 is 249, with the cursor sitting at the 250th position. I’d have to look at the code to see why it stops there, since 249 isn’t the 251 I expected.

Something interesting happens when you press ENTER. That line will get tokenized, and BASIC keywords will be changed from the full word (such as “PRINT”) in to a one or two byte token that represents them. In this case, the PRINT keyword will become a one byte token, so the five bytes I typed for PRINT will become one byte. And then if I try to EDIT the line again, I should be able to “X”tend the line and add four more characters:

You can see after I typed “EDIT 10” and then typed “X” to extend to the end of the line, I could type four more characters.

BUT, if I then LIST the program, you won’t see all four of them — only three:

This is a bug in LIST. The four dots actually are still there, and you can see them PRINT when I run this program:

I suppose the point is, no matter what you do, you can’t enter more than 249 characters on a BASIC line.

Or can you?

Defining the limits

What was the limit set to 251? Why not 256 or 200 or something else? It seems to me that the LINBUF length limit may have been arbitrary based on how much memory was available. I suppose back in 1980 on a 4K machine, you didn’t want to take up half your memory for an input buffer that was unused any time you weren’t actually typing stuff in.

But, the actual BASIC interpreter doesn’t seem to care about line length. Looking at the Unravelled disassembly, here is the description of how a BASIC program is stored in memory:

Let’s ignore #1 for the moment. We’ll use this simple program as an example:

10 PRINT"A"
20 PRINT"B"
30 PRINT"C"

If I type that in, somewhere in memory it will be stored. The keyword PRINT will be turned in to a one byte token, and the rest — the quotes and letters — will be be stored as-is. The somewhere we can figure out by checking some memory locations:

Above, TXTTAB represents two bytes in memory at &H19 (25) and &H20 (26) that contain the address where the BASIC program is in memory. Since variables are stored directly after the BASIC program, we can use VARTAB (the start of variables) to figure out where BASIC ends.

PRINT PEEK(25)*256+PEEK(26)

PRINT PEEK(27)*256+PEEK(28)

This shows that my three line program is in memory from 9729 to 9758. Well, actually, 9757 would be the last byte of the BASIC program, since 9758 is the first byte of variable storage. But close enough!

If I were to PEEK the bytes in that range, I could see what the tokenized program looks like.

FOR I=PEEK(25)*256+PEEK(26) TO PEEK(27)*256+PEEK(28):PRINT PEEK(I);:NEXT

Or, print the two sets of PEEKs first and just use those numbers in the FOR loop:

Above we see the series of bytes that make up the BASIC program. In the earlier list…

…number 4 said the program ends with “two zero link bytes”, and we see a 73. Why? Because that 73 is the first byte of the variables after the program. #TheMoreYouKnow

Looking at those bytes, here is what they represent:

38 10 - address of next line
00 10 - line number 10
135   - PRINT keyword token
34    - quote
64    - A
34    - quote
0     - end of line
38 19 - address of next line
00 20 - line number 20
135   - PRINT keyword token
34    - quote
66    - B
34    - quote
0     - end of line
38 28 - address of next line
00 30 - line number 30
135   - PRINT keyword token
34    - quote
67    - C
34    - quote
0     - end of line
00 00 - address of next line (0 0 means end of program)

The “line number” ones are pretty simple. That’s the line number represented as two bytes.

Side note: Two bytes should allow for lines 0 to 65535, but BASIC only allows lines 0 to 63999. If you try to make a line 64000 or higher, you will get a ?SN ERROR. I guess they didn’t have room for a special “Line Number Too Large” error.

The “address of next line” one corresponds to the location in memory where the next line’s “address of next line” bytes will be. Thus, if you had a BASIC program starting in memory at 10000 (to to make the numbers look nice), it might look like this:

 Mem.        +----- 6 bytes of data ------+
 Addr        |                            |
10000: [4006][10][PRINT_TOKEN]["][A]["][00]
10006: [4013][10][PRINT_TOKEN]["][B]["][00]
10013: [40xx][10][PRINT_TOKEN]["][C]["][00]
10019: [0000]

At least, I think that’s pretty close.

You will notice that BASIC knows the address for the start of the next line, and uses a zero to represent end-of-line. There is no “line length” in there, which means BASIC is kind of like the honey badger… it doesn’t care about line length!

This makes me think that the limit is primarily the LINBUF buffer size. If we had a way to type longer lines, BASIC seems like it would handle them just fine. And this gives me a few ideas:

  1. Patch BASIC to use a larger input buffer so longer lines can be typed. This might also require patching other routines I haven’t looked at. For example, I think there’s some limit to what LIST does. This sounds like work and something that requires more knowledge than I have.
  2. Manually manipulate the BASIC program to create larger lines that can’t be typed. Programs such as Carl England’s CRUNCH will pack lines together to make them longer than you could actually type. (But how long? I dig in to this in a later article.)
  3. Something else…

In the next installment, we will explore some of these options…

Until then…

What is a CoCo? ChatGPT has some thoughts…

Meanwhile, over at chat.openai.com… I told it:

The CoCo is the nickname for the Radio Shack TRS-80 Color Computer. Can you come up with something that “COCO” stands for, that would be related?

– Me, to ChatGPT

It responded with some interesting suggestions…

Interesting question! The CoCo is indeed a nickname for the Radio Shack TRS-80 Color Computer, a line of home computers developed and sold by Tandy Corporation from 1980 to 1991¹. It was based on the Motorola 6809E processor and used the MC6847 Video Display Generator (VDG) chip¹³. It was also compatible with Microsoft BASIC and OS-9 operating system¹.

Some possible things that COCO could stand for, related to this computer, are:

  • Color Operating Computer Organization: A name for a group of enthusiasts or hobbyists who use or collect CoCo computers.
  • Colorful Objects with Computational Outputs: A description of the graphical capabilities of the CoCo, which could display up to 16 colors on screen⁴.
  • Commonly Observed Computerized Oracle: A humorous way of referring to the CoCo as a source of information or entertainment for its users.
  • Cooperative Online Colorful Operations: A term for multiplayer games or applications that could run on the CoCo using its serial port or modem⁴.

Source: Conversation with Bing, 3/16/2023
(1) TRS-80 Color Computer – Wikipedia. https://en.wikipedia.org/wiki/TRS-80_Color_Computer Accessed 3/16/2023.
(2) TRS-80 Color Computer – CoCopedia – The Tandy/Radio Shack Color …. https://www.cocopedia.com/wiki/index.php/TRS-80_Color_Computer Accessed 3/16/2023.
(3) Radio Shack TRS-80 Color Computer. https://oldcomputers.net/coco.html Accessed 3/16/2023.
(4) trs 80 computer products for sale | eBay. https://www.ebay.com/b/trs-80-computer/bn_7024741177 Accessed 3/16/2023.

– chat.openai.com

And, there was more…

  • Colorful Output with Creative Options: A way of highlighting the CoCo’s versatility and potential for different types of programs and projects.
  • Computerized Optical Colorful Oscilloscope: A reference to the CoCo’s ability to display graphics and sound using its VDG chip and 6-bit DAC.
  • Compact Original Colorful Object: A simple description of the CoCo’s physical appearance and design.
– chat.openai.com

There are only some many words beginning with C or O that make sense for this topic, but it might be fun to keep asking it until we find the ultimate backronym.

Until then…

These lines are just too long (for Color BASIC)!

These lines are just too long!

– Me at Walt Disney World

There is a limit to how long of a line you can type in Color BASIC, whether that be entering a program line, or typing at an INPUT prompt. The input buffer is defined as 251 bytes, as shown in the Color BASIC Unraveled book:

LINBUF is defined as 250.

So of course, as you type in really long lines, it stops after the … 249th character. The reason why is beyond the scope of this posting, but this topic will be discussed in an upcoming article series.

Here is an example:

The same is true for using the LINE INPUT command.

Even if the input buffer were larger, the largest string variable you can have is 255 characters, so that would be the maximum input size to avoid an ?LS ERROR (length of string). Here’s a short program to verify that, which uses the STRING$() function to create a string of a specified length containing a specified character.

CLEAR 300
A$=STRING$(255,"X")
A=A$+"X"
?LS ERROR

Something I had not thought about until today was that this length impacts anything that INPUTs, whether it is from the user typing on the keyboard, or reading data from a file.

When reading from a tape or disk file, INPUT and LINE INPUT expect to see zero or more characters with an ENTER at the end. But when creating a file, you can make a line as long as you want simply by using the semi-colon at the end of a PRINT command so it does not output an ENTER. Consider this example that prints a 510 character string with an ENTER at the end:

10 OPEN "O",#1,"510LINE"
20 PRINT #1,STRING$(255,"X");STRING$(255,"X")
30 CLOSE #1

That works fine, but how do you read it back? The string is now too long to be read in to INPUT or LINE INPUT. What will it do?

10 OPEN "I",#1,"5120LINE"
20 LINE INPUT #1,A$:PRINT A$
30 CLOSE #1

In this case, the disk input routine reads as much data as will fill the input buffer, then keeps scanning forward looking for the ENTER. The rest of the data on that string appears to be discarded.

If you were to count the Xs, or just PRINT LEN(A$), you would see that A$ contains the first 249 characters of the 510 string that was written to the file.

But what if the ENTER was not at the end? By adding a semi-colon to the end of the PRINT:

10 OPEN "O",#1,"510LINE"
20 PRINT #1,STRING$(255,"X");STRING$(255,"X");
30 CLOSE #1

…what would the input program do? (Same code as before, just now it’s reading from a file that has a 510 character line with no ENTER at the end.)

10 OPEN "I",#1,"5120LINE"
20 LINE INPUT #1,A$:PRINT A$
30 CLOSE #1

…the behavior will change. We will now get an ?IE ERROR (Input Past End of File). The INPUT and LINE INPUT routines need an ENTER, so while it scans forward looking for the end of the line, it hits the end of file before an ENTER, and that’s what it reports.

This tells us two things:

  1. Always have an ENTER at the end of a line.
  2. Don’t have the line longer than 249 bytes or any characters after it will be ignored.

Here is an example that creates a file with a 250-character line that contains 248 “X” characters followed by “YZ”. When read back, it only gets up to the Y (249 characters):

5 CLEAR 300
10 OPEN "O",#1,"250LINE"
20 PRINT #1,STRING$(248,"X");"YZ"
30 CLOSE #1
40 OPEN "I",#1,"250LINE"
50 LINE INPUT #1,A$:PRINT A$
60 CLOSE #1

And, since BASIC appears to keep scanning INPUT looking for an ENTER, you could make your tape or disk I/O take a really long time if you created some really huge line with no ENTER at the end, then tried to read it later.

Here is a program that writes 65,025 “X”s to a file with no ENTER at the end, and then tries to read it back:

0 'LINE2BIG.BAS
10 CLEAR 300
20 PRINT "WRITING FILE..."
30 OPEN "O",#1,"LINE2BIG"
40 FOR I=1 TO 255
50 PRINT I;
60 PRINT #1,STRING$(255,"X");
70 NEXT
80 CLOSE #1
90 PRINT:PRINT "READING FILE..."
100 OPEN "I",#1,"LINE2BIG"
110 LINE INPUT #1,A$:PRINT A$
120 CLOSE #1

If you run that, it will eventually try to read (and take a really long time) before erroring out with ?IE ERROR. Add “75 PRINT #1” to insert an ENTER at the end of the PRINT loop, and then it will just take a really long time trying to read, and print the first 249 characters of that 65,025 character string.

And that’s it for today, but I hope you took notes. LINBUF will return.

Until then…

Color BASIC program line info dump program

These days, I feel like I am regularly saying “I’ve learned more this week about X than I learned in Y years of using it back in the 1980s!”.

This is another one of those.

Each line of a Color BASIC program is tokenized (changing keywords like PRINT to a one or two byte token representing them) and then stored as follows:

  • 2-Bytes – Address in memory where next line starts
  • 2-Bytes – Line number (0-63999)
  • n-Bytes – Tokenized program line.
  • 1-Byte – Zero (0), indicating the end of the line

The four byte header and the 1 byte zero terminator mean that each line has an overhead of 5-bytes. You can see this by printing free memory and then adding a line that has a one byte token, such as “REM” or “PRINT”:

Above, you see the amount of memory decreases by 6 bytes after adding a line. That’s five bytes for the overhead, and one byte for the “REM” token.

The BASIC program starts in memory at a location stored in memory locations 25 and 26. You can see this by typing:

PRINT PEEK(25)*256+PEEK(27)

There are other such addresses that point to where variables start (directly after the program), and where string memory is. Here is an example program from an earlier article I wrote that shows them all. (The comments explain what each location is.)

0 ' BASINFO3.BAS

10 ' START OF BASIC PROGRAM
20 ' PEEK(25)*256+PEEK(26)

30 ' START OF VARIABLES
40 ' PEEK(27)*256+PEEK(28)

50 ' START OF ARRAYS
60 ' PEEK(29)*256+PEEK(30)

70 ' END OF ARRAYS (+1)
80 ' PEEK(31)*256+PEEK(32)

90 ' START OF STRING STORAGE
100 ' PEEK(33)*256+PEEK(34)

110 ' START OF STRING VARIABLES
120 ' PEEK(35)*256+PEEK(36)

130 ' TOP OF STRING SPACE/MEMSIZ
140 ' PEEK(39)*256+PEEK(40)

150 ' USING NO VARIABLES
160 PRINT "PROG  SIZE";(PEEK(27)*256+PEEK(28))-(PEEK(25)*256+PEEK(26)),;
170 PRINT "STR SPACE";(PEEK(39)*256+PEEK(40))-(PEEK(33)*256+PEEK(34))
180 PRINT "ARRAY SIZE";(PEEK(31)*256+PEEK(32))-(PEEK(29)*256+PEEK(30)),;
190 PRINT " STR USED";(PEEK(39)*256+PEEK(40))-(PEEK(35)*256+PEEK(36))
200 PRINT " VARS SIZE";(PEEK(29)*256+PEEK(30))-(PEEK(27)*256+PEEK(28)),;
210 PRINT " FREE MEM";(PEEK(33)*256+PEEK(34))-(PEEK(31)*256+PEEK(32))

I thought it might be interesting to write a BASIC program that displays information on each line of the BASIC program. That information would include:

  • Start address of the line
  • Address of the next line
  • Line number of the line

Here is what I came up with. It can use generic PRINT in lines 40 and 70 (for Color BASIC) or a nicer formatted PRINT USING (for Extended Color BASIC) in lines 50 an 80.

0 'BASINFO.BAS
1 REM BASINFO.BAS
2 REMBASINFO.BAS
10 PRINT " ADDR NADDR LINE# SIZ"
20 L=PEEK(25)*256+PEEK(26)
30 NL=PEEK(L)*256+PEEK(L+1)
40 'PRINT L;NL;
50 PRINT USING"##### #####";L;NL;
60 IF NL=0 THEN END
70 'PRINT PEEK(L+2)*256+PEEK(L+3);NL-L
80 PRINT USING" ##### ###";PEEK(L+2)*256+PEEK(L+3);NL-L
90 L=NL:GOTO 30

For this program, as shown, running on a virtual 32K Extended Color BASIC CoCo in the XRoar emulator, I see:

The first column (ADDR) is the address of the BASIC line in memory. After that is the address of where the next line begins (NADDR), and it will match the address shown at the start of the following line. The third column is the line number (LINE#), and last is the size of the line (SIZ) which includes the tokenized line AND the terminating zero byte at the end of it.

The final line has a “next address” of zero, indicating the end of the file.

At the start of the program I included three comments:

0 'BASINFO.BAS
1 REM BASINFO.BAS
2 REMBASINFO.BAS

In the output of the program, you see them described as:

 ADDR NADDR LINE# SIZ
 9729  9747     0  18  <- [0 'BASINFO.BAS]
 9747  9765     1  18  <- [1 REM BASINFO.BAS]
 9765  9782     2  17  <- [2 REMBASINFO.BAS]

You can see that the length of lines 0 and 1 are both 18, even though one looks like it should be shorter. In this case, the apostrophe (‘) abbreviation for REM seems to take as much space as “REM ” (with a space after it). This is because the apostrophe is encoded as a “:REM” (colon then REM). Alex Evans recently reminded me of this. This behavior would allow you to use it at the end of a line like this:

10 LINE INPUT A$'ASK FOR USERNAME

…instead of having to do:

10 LINE INPUT A$:REM ASK FOR USERNAME

But don’t do either! REMs at the end of the line can be the worst place to have REMs, since BASIC will have to scan past them to get to the next line, even if they are after a GOTO. This makes them slower. (Reminder to self: do an article on this since I’ve learned more since I original covered the topic in one of my Benchmarking BASIC articles…)

But I digress…

If you wanted to run this on your own program, you could do so by making this routine load at a high line of BASIC (higher than any lines you might be using), then you could save it as ASCII (SAVE”BASINFO”,A) and then use MERGE”BASINFO” (from disk) to bring those lines in to your program.

63000 PRINT " ADDR NADDR LINE# SIZ":L=PEEK(25)*256+PEEK(26)
63001 NL=PEEK(L)*256+PEEK(L+1):PRINT USING"##### #####";L;NL;:IF NL=0 THEN END ELSE PRINT USING" ##### ###";PEEK(L+2)*256+PEEK(L+3);NL-L:L=NL:GOTO 63001

Now you could do RUN 63000 to see what your program looks like. (The highest line number Color BASIC allows is 63999 so you could change that to 63998 and 63999 if you wanted absolutely the most line numbers available for your program ;-)

You could also add “IF L=63000 THEN END” somewhere and have it stop when it hits that routine.

What use is this?

For an upcoming article, I expect to use a version of this code to “prove” something as it relates to BASIC and the length of lines.

But, it might also be fun to generate some statistics — longest line, shortest line, a graph of the different line lengths, etc.

Until next time…

FreeRTOS: Virus?

The discussion about using FreeRTOS has come up at my day job in recent years, so a month or two ago I downloaded it from the official website with plans to take a look at it.

I never got around to it, but today our IT department contacted me about its removal from my system. The download was flagged to contain a virus. I am sharing this information here so it will show up in search engines:

Severe Gen:Variant.Tedy.307215 - Virus.Generic - Deleted

File - C:Users<username>DownloadsFreeRTOSv202212.00FreeRTOSv202212.00FreeRTOSDemoWIN32-MSVCDebugRTOSDemo.exe

md5: 2F1699A8E9DE9D946FD6DDCC4BCC2F45

It appears all I did was open the project and build it in Visual Studio. Nothing else on the system was flagged, and we use Visual Studio to build two of our in-house apps, so I am curious why this happened.

Anyone seen this? Please leave a comment.

Stay safe :)

An adventure game rabbit hole…

From the “this is how my mind works sometimes” department…

Some definitions, as they relate to my career as an embedded programmer:

  • Can of Worms – when you go to fix something that should be simple and quick, but as you try to fix it, you realize it’s tied to other things that will also need fixes and thus your one quick fix turns in to days of many not-so-quick fixes.
  • Whac-A-Mole(tm) – when you fix a bug only to find that it manifests another bug, and then you fix that bug and it causes yet another bug, and then… (See also: Unintended Consequences)
  • Rabbit Hole – when you think something will be simple, then you look, and have to go to another section of code to find what you are looking for, but that sends you to another section of code which also sends you to another section of code and that…

Today, a quick rabbit hole in relation to text adventure games.

Down the rabbit hole: the beginning of playing

I was fascinated with text adventure games when I first learned of them back around 1981 or 1982. I do not recall what the first one I saw was, but it was likely something at the local Radio Shack playing on a TRS-80 Model 3. I definitely remember buying Mission Impossible by Scott Adams on a cartridge for my Commodore VIC-20.

This led me to trying to write my own text adventures. The “most successful” one was probably a custom game I wrote for the *TALK TO ME* BBS in Houston, Texas. The game was a recreation of the SysOp’s (system operator) apartment, and the goal was go roam around the apartment collecting parts of the system that ran the BBS and hook them all back up. It was a very simple game, with the only challenge being something random you couldn’t do anything about — a killer cockroach (if I recall) that would randomly show up and scatter all your work back randomly across the apartment. Randomly. One day, I hope to find this program on a cassette tape somewhere.

But I digress…

Down the rabbit hole: the beginning of coding

Over the years, I wrote various routines for doing text adventures — word wrap, verb/noun input, moving from room to room, getting and dropping objects, etc. When I started going through my old CoCo stuff years ago, I found notes on old games I’d forgotten I had been working on. The most complete one was “The ODDyssey” which I was co-writing with my MC-10 buddy Paul T. (We had intended it to run on both CoCo and MC-10.) I have the engine of that game and the complete map, but we never finished the goals and objects beyond some early test ones.

Still, it looked pretty good, and using a trick I think I read about in The Rainbow magazine, you could type in “get the black book” instead of just “get book.”

The ODDyssey, my uncompleted text adventure from around 1984.

In addition to various CoCo adventures, I also tried writing one in Java that would run as an applet on a web browser. That game, based on Disneyland/Disney World’s Haunted Mansion, even included background music :)

http://www.disneyfans.com/adventure/index.shtml

Heck, I even wrote a “WebVenture Generator” MS-DOS program that would let me make simple walk-through web pages using digital photos I took. Here is one from Disneyland using photos I took in August 1996:

http://www.disneyfans.com/dlmstour/index.htm

None of this has anything to do with today’s topic except to say that I’ve toyed with adventure games numerous times over the years in several different languages.

Down the rabbit hole: the beginning of the point

When I’m starting a new adventure game, I usually make a test map. Ideally I have rooms that have exits in different directions so I can verify all of that works. It might start as something very simple like this:

[ 1 ] --- [ 2 ]
  |         |
[ 3 ] --- [ 4 ] --- [ 5 ]

That has rooms that have exits leading North, South, West and East, but it doesn’t truly exercise all the possibilities. A proper “engine” shouldn’t care, but if one wanted to be thorough, you’d want to make sure you have every possible combination of room exits covered in a test map.

How many exits can one room have?

For simplicity, we will limit our exits to the four compass directions – North, South, West and East. A fancier adventure game might allow diagonals (Northwest, Southeast) and vertical (Up and Down). But for now, we’ll stick to the four basic directions.

Initially I started drawing out a map by hand, trying to make sure I had a room with only an exit East, a room that had only an exit West, etc. I quickly lost track of what I was trying to do…

So I wrote a program to show me all the combinations. Since there are four exits that can either exist or not exist, I decided to use four bits to represent them.

  • Bit 0 – North
  • Bit 1 – South
  • Bit 2 – West
  • Bit 3 – East

Side Note: I am using NSWE here, but I recall, when I first learned about writing adventure games from some VIC-20-era magazine I had, they used the order of NEWS – North, East, West, South. That might be easier to remember, but the order doesn’t matter for this example.

Here are what those bits would look like:

0001 - Exit North
0010 - Exit South
0100 - Exit West
1000 - Exit East

…then all the other combinations. Once I thought about it this way, it was easy to see I had everything from 0000 (0, no exits) to 1111 (15, exit in all four directions). Thus, there would be 16 possible room types.

I wrote this program to print them all out:

10 PRINT "ALL POSSIBLE EXITS:"
20 FOR D=1 TO 15:PRINT D;
30 IF D AND 1 THEN PRINT "N";
40 IF D AND 2 THEN PRINT "S";
50 IF D AND 4 THEN PRINT "W";
60 IF D AND 8 THEN PRINT "E";
70 PRINT,;
80 NEXT

Side note: If you wanted to include the diagonal directions of NW, NE, SE and SW, you’d expand that to eight bits. If you were also including Up and Down, add two more bits. That would make 1024 possible room types — and now you see why I limited it to just four directions.

And from there, I started drawing a map, making sure that I had at least one room of each of the 15 times listed above. (A room with no exits would also be possible, though maybe not useful.)

And this led me even further down this rabbit hole… I wanted to make the most compact map I could that had a room of each type in it. That would be my “test map” for the adventure game project.

Down the rabbit hole: the beginning of brute force

After initially trying to map out all the combinations using brute-force…

[10]-[6]  [2]  [10]-[4]
 |    |    |    |
[3]  [11]-[15]-[7]
 |    |    |    |
[1]  [9]--[15]--[5]
           | 
[8]--[12]-[5]

TODO: 13 NWE, 14 SWE

…I wondered if there might be an easier way. Instead of just spending ten minutes drawing this by hand, I could probably spend a few hours or days to come up with a program that would help me create the map.

And as I thought about that, it sorta sounded like one of those sliding puzzle games I enjoyed as a kid.

Micha L. Rieser, via Wikipedia

In a number puzzle, the goal is to get the numbers in order. For a photo puzzle, it was to get the photo assembled. For my project, there could be multiple solutions — just as long as all rooms had a/an exit(s) that connected to another room’s exit(s).

And that reminded me of a game I had for my Sega Genesis called Junction. Or the relatively new CoCo game Pipes by Nick Marantes.

In those games, you are either sliding pieces around to complete a track for some object to follow, or you are placing pieces down (ala Tetris) to create a track.

For my use, instead of having the “playfield” be locked to some fixed width and height, it could be anything — very wide, very tall, or both. The more compact, the higher the score. The score might be calculated by the fewest number of duplicate pieces (15 being the least you could use, since you had to have at least one of each room type) and the least amount of empty spaces in the final map. For example, if there were only four pieces, they could be represented in a 4×4 grid with no empty spaces — a perfect score:

[1]--[2]
 |    |
[3]--[4]

But if the same four pieces had been represented like this:

[1]--[2]  xxx
      |
xxx  [3]--[4]

…it is now using a 3×2 grid with two empty spaces and that would be a lower score. Somehow.

And if I could create something like this, and get people to play it, they could work out the best and most efficient way(s) to represent a text adventure demo map that covered all possible room types.

And that, my friends, sounds like even more work than just brute forcing a map until I am happy with it.

So naturally, I may just have to do this.

Until next time…

POKE 113,0:EXEC 40999 (and DLOAD)

If you recognize this, you must have been a Radio Shack Color Computer owner. If not, these two instructions would reboot a CoCo, starting it back over at the boot up screen just like when it is first powered on.

When the CoCo 3 was introduced, a different shortcut for rebooting was discovered thanks to a hidden easter egg. The egg was a hidden graphic showing a digitized photo of there three programmers who worked on the BASIC ROM. To make it display, you held down CTRL and ALT while powering on the machine. (A nod to Ctrl-Alt-Del reset on a PC, perhaps?)

We quickly discovered that the easter egg would also show if you held down CTRL+ALT and hit the reset button. Once the image was on the screen, pressing the reset button would reboot the computer back to the startup screen!

Thus, on a CoCo 3, POKE 113,0:EXEC 40999 was replaced by “CTRL+ALT+RESET, RESET” saving much typing.

But I digress…

Have you ever wondered just what that POKE and EXEC were doing? Neither did I, but I decided to find out anyway.

POKE 113,0

To figure this out, I turn to the book Color BASIC Unravelled by Spectral Associates. This book contains a disassembly of the Color BASIC ROM, fully commented with explanations of what everything does.

113 in decimal is hexadecimal &H71, which we need to know because the disassembly uses HEX instead of decimal for memory locations. Looking at memory location &H71 shows us this:

Spectral Associates named it RSTFLG (Reset Flag) and based on the value there, the system will either WARM start or COLD start when the reset button is pressed. By default, this value has &H55 there:

When the reset button is pressed, the CoCo’s 6809 processor jumps to a reset vector that is in memory at the last two bytes — &HFFFE-&HFFFF. Whatever address is stored there will be where the 6809 starts executing code. This address comes from the last two bytes of the 8K Color BASIC ROM at memory location &HBFFE-&HFFFF. For “reasons” beyond the scope of this article, the upper memory is ghosted so that value also appears at &HFFFE-&HFFFF.

Thus, when reset is pressed, execution begins at whatever address RESVEC is which you can see in the left columns of the disassembly is at &HA027 in the Color BASIC ROM. Searching in the Unraveled book for &HA027 (or better, the RESVEC label) shows us initialization code that eventually jumps to this:

There you can see an LDA RSTFLAG which is loading register A with whatever is at memory location 113 and comparing it to &H55. Based on the result, it either jumps to the cold start routine or warm start routine.

POKE 113,0 just puts the 0 in there so the code ends up jumping to BACDST which is what starts everything up and displays the BASIC copyright screen.

Thus, if you do POKE 113,0 and then press RESET, the CoCo will cold start.

EXEC 40999

By now, you may realize what code is at 40999. In HEX that is &HA027 … does that look familiar? That’s the address that the reset vector points to, which eventually leads to the routine at &HA00E which checks the RSTFLG (memory location 113) to decide what to do.

EXEC 40999 just jumps to the location that the reset button would causes the 6809 to jump to.

Thus, POKE 113,0 sets the “cold start” flag, and EXEC 40999 (or pressing reset) invokes the code that checks that flag and acts upon it.

You are welcome.

Bonus – DLOAD

There was another shortcut discovered on the CoCo 3 which allowed a cold start to be done without using EXEC 40999. Instead, you can type:

POKE 113,0:DLOAD

…and the CoCo 3 will cold start. Typing DLOAD by itself will cause the CoCo 3 to pause and then clear the screen. Any guess at what is going on?

DLOAD was a command that came with Extended Color BASIC, and it is a command I recall using exactly one time back then, though I later used it often on the CoCo 3 — just not for its original purpose.

I do not have a copy of the original TRS-80 Color Computer Getting Started with Extended Color BASIC manual for the CoCo 1, but since DLOAD is not mentioned in the 1984 version of the same manual for the CoCo 2, I suspect it may not have ever been documented in those manuals. (Leave a comment if you have information about this.)

It is, however, listed in the spiral-bound TRS-80 Color Computer Quick Reference Guide that I probably consulted more than any other book I have ever owned.

DLOAD was similar to the cassette CLOAD command, except it loaded via the Serial I/O port at either 300 or 1200 baud. If you had a serial cable connected to another CoCo, or a modem dialed in to another CoCo, it was possible to DLOAD a program that way.

And this is how I downloaded my first real terminal program on my CoCo 1 — someone sent it to me via DLOAD!

The only problem with this command is that there was no DSAVE command built in, so the only way to upload to DLOAD was using a special program. I no longer recall what that program was, or who wrote it, or where it came from. (If you know, please leave a comment.)

In Extended Color BASIC Unravelled, there is this note about DLOAD:

DLOAD is the most obscure command in the Color Computer and absorbs a substantial amount of space in the ROM. DLOAD is so poorly understood because Tandy has never made the necessary companion routine, DSEND. DLOAD will DOWNLOAD a file over the RS 232 line from another system, however there is no companion routine, which will transmit a file over the RS 232 line to another Color Computer. Once a DSEND routine is built and made available to the masses, DLOAD will be much better understood.

– Extended Color BASIC Unravelled, page 5.

From looking at the source, it appears there was both DLOAD and DLOADM variations, just like there is CLOAD/CLOADM for tape and LOAD/LOADM for disk, with the “M” version being for machine language programs. If this is true, then that Quick Reference manual entry is incorrect and DLOAD does not load a machine-language program — it may have loaded a BASIC program, with DLOADM being the way to load machine language over the Serial I/O port.

But I digress.

But what does this have to do with the CoCo 3 and resetting the computer? I’m glad you asked! (You did ask, didn’t you?)

The lack of a DSAVE command (or DSEND as Unravelled called it, not matching the other existing commands) meant that DLOAD really wasn’t that useful. Heck, without it being documented in the manual, how would anyone even know it existed? This may have something to do with this command being removed from the CoCo 3. The memory for that command was repurposed for other patches to Extended Color Basic needed to support new features of the CoCo 3’s hardware.

DLOAD started at &H8C1B in the Extended BASIC ROM. On the CoCo 3, this space is used for a different purpose. Super Extended BASIC Unravelled has this note:

The initialization routine for the Color Computer 3 begins at $8C1B. This code writes over the DLOAD routine that was in the original Color Computer . . .

– Super Extended BASIC Unravelled, page 30

In the disassembly, that location is still labelled as DLOAD, and the original BASIC keyword of DLOAD is still in Extended BASIC pointing to it. BUT, the CoCo 3 (which copies all the ROMs in to RAM on startup) patches that location with new code. It also modifies the Interrupt Vectors so the RESET vector points there instead of the original routine in the Color BASIC ROM:

Thus, on a CoCo 3, pressing reset jumps to the location in memory where the DLOAD code used to be, but which is now the reset routine for the new CoCo 3 hardware.

The complete quote from the Unraveled book is actually…

The initialization routine for the Color Computer 3 begins at $8C1B. This code writes over the DLOAD routine that was in the original Color Computer (actually, typing DLOAD will simulate pressing the reset button). This initial- ization routine is used for both a warm start (simply getting control of the computer back from a runaway program) and a cold start (where the computer and Basic have to be reinitialized).

– Super Extended BASIC Unravelled, page 30

POKE 113,0:DLOAD on a CoCo 3 performs the same function as POKE 113:EXEC 40999 on a CoCo 1 or CoCo 2.

But … there is something different. If you do POKE 113,0:DLOAD on a CoCo 3, you get a short pause before the system cold starts. If you do POKE 113,0:EXEC 40999, it quickly restarts.

This is because EXEC 40999 is still jumping in to the original &HA027 routine in the Color BASIC ROM code. This bypasses all the new cold start code for the CoCo 3! Doing the old style POKE/EXEC may look like it reset the CoCo 3, but it wasn’t actually doing everything and, possibly, there could have been some instances where hardware configurations had changed that this would not reset/restore properly.

I most certainly did not realize that back then. I suppose the use of POKE 113,0:DLOAD (or POKE 113,0 then reset) was actually the “more appropriate” way to reset the CoCo 3, which is probably how I learned about it somewhere.

And now you know … more than you wanted to … about POKE 113,0 and EXEC 40999 and DLOAD on a CoCo 3.

Until next time…

P.S. While researching for this article, I started walking through all the things that go on during the CoCo 3 startup. It’s quite a lot with all the ROM to RAM and patching going on. Maybe I’ll have to dig in to that and write about it someday.