Category Archives: Color BASIC

Optimizing Color BASIC – part 1

See also: part 1, part 2, part 3, part 4, part 5, part 6, part 7, part 8 and part 9.

A recent post by Art “ADOS” Flexser on the CoCo mailing list mentioned a few commercial programs for the Radio Shack Color Computer that were designed to optimize BASIC programs:

On Jan 2, 2015, at 12:26 AM, Arthur Flexser wrote:

A little googling identified the utility I was referring to as “The Stripper” from Eigen Systems. Apparently, there was also a similar program by Bob van der Poel called “Packer”. Maybe one or the other is available online someplace.

These should be useful for those finding tight memory for Basic programs in
the 4K contest.

The 4K contest he mentions is a programming challenge I recently organized to see what someone could do if they had the original 1980 4K Color Computer. You can read about it here:

http://www.cocopedia.com/wiki/index.php/1980_4K_CoCo_Programming_Challenge

In addition to the programs Art mentioned, I also had one given to me by Carl England. I used it on one of my programs at Sub-Etha Software. I do not know how it compares with The Stripper or Packer, but beyond true optimization of the code, it did about all you could do. Here are the tricks he used which produce the smallest and fastest version of a BASIC program, but they usually rendered it un-editable and hard to read.

NOTE: Code can be written for size or speed. Carl’s program optimized for size which in some cases made it faster since BASIC had less to parse, but as you will see, some optimizations might increase execution time making it slower. Maybe.

Remove all spaces

The less bytes to parse, the smaller and faster it will run. It has to be smart enough to know when spaces are required, such as “FORA=1TOQ STEP1” when a variable needs a space after it before another keyword.

Pack/combine lines where possible.

Any line called by a GOTO had to remain at the start, by following lines would be combined up to the max size of a BASIC line. Only when it had to be broken by logic (ELSE, etc.) would it not be combined. For example:

10 FOR I=1 TO 100
20 GOSUB 50
30 NEXT I
40 END
50 REM PRINT SOMETHING
60 PRINT "HELLO WORLD!"
70 RETURN

…would end up as:

10 FORI=1TO100:GOSUB60:NEXTI:END
60 PRINT"HELLO WORLD!":RETURN

Remove all REMs.

Obvious. I *think* his program would adjust a GOTO/GOSUB that went to a REM line to go to the next line after it, and remove the REM:

10 GOSUB 1000
20 END
1000 REM PRINT SOMETHING
1010 PRINT "HELLO"
1020 RETURN

…would become like:

10 GOSUB1010:END
1010 PRINT"HELLO":RETURN

NOTE: Even without packing, I learned not to GOTO or GOSUB to a REM since it required the interpreter to parse through the line. I would GO to the first line of code after the REM:

10 GOSUB 1010
...
1000 REM MY ROUTINE
1010 PRINT "HERE IS WHERE IT STARTS"
...

Every thing you do like that saves a bit of parsing time since it doesn’t have to start parsing 1000 and look up a token then realize it can ignore the rest of the line.

Not bad so far

But that’s not all!

The BASIC input buffer is some maximum number of bytes (250?). When you type a line to max, it stops you from typing more. When you hit ENTER, it is tokenized and could be much smaller. For instance, “PRINT” turns in to a one-byte token instead of the five characters. Carl’s program would combine and pack the tokens up to the max line size. Thus, it created lines that BASIC could run which were IMPOSSIBLE to enter on the keyboard. If I recall, you could LIST and they would print (?) but if you did an EDIT you were done.

Renumber by 1.

GOSUB2000 would take up one bye for the token, then four bytes for the line number. If you renumber by 1s, it might make that function be GOSUB582 and save a byte. Multiply that by every use of GOTO/GOSUB/ON GOTO/etc. and you save a bit.

Even without one of these compressors, try a before/after just doing a RENUM 1,1,1. I recently posted an article with a short (27 line) word wrap routine in BASIC. Doing this renum saved 7 bytes.

Removing NEXT variables.

I am not sure if this is just something I knew, or if his program also did it. If you use FOR/NEXT and it is used normally, you don’t need the NEXT variables like this:

10 FOR D=0 TO 3 'DRIVES
20 FOR T=0 TO 34 'TRACKS
30 FOR S=1 TO 18 'SECTORS
40 DSKI$ D,T,S,S1$,S2$
50 NEXT S
60 NEXT T
70 NEXT D

The NEXTs could also be

50 NEXT S,T,D

Or

50 NEXT:NEXT:NEXT

Ignoring spaces, “NEXT:NEXT:NEXT” takes up one less byte than “NEXTS,T,D” (NEXT is turned in to a token, so it is TOKEN COLON TOKEN COLON TOKEN versus TOKEN BYTEVAR COMMA BYTEVAR COMMA BYTEBAR”.

This takes up less memory, but there is a speed difference:

10 T=TIMER
20 FOR I=1 TO 10000
30 REM DO NOTHING
40 NEXT I
50 PRINT TIMER-T

Run this and it shows something in the 1181 range (speed varies based on other things BASIC does; pound on keys while it runs and it takes more time, for example). Change the “NEXT I” to “NEXT” and it shows something in the range of 1025. The more FOR variables, the more the savings, too.

10 TM=TIMER
20 FOR D=0 TO 3
30 FOR T=0 TO 34
40 FOR S=1 TO 18
50 REM DO SOMETHING
60 NEXT S
70 NEXT T
80 NEXT D
90 PRINT TIMER-TM

…shows around 345. Changing it to:

60 NEXTS,T,D

…changes it to around 336 – slightly smaller and faster since it is not parsing three different lines.

60 NEXT:NEXT:NEXT

…changes it to around 293! One byte smaller and slightly faster.

AND THERE’S MORE! But this post is already too long.

What I think would be cool is an actual BASIC OPTIMIZER that could do some smart things to programs, much like we do with the C language optimizers for asm and C. For example, noticing stupid things:

A$ = B$+"."+"."+C$

to

A$ = B$+".."+C$

And removing NEXT variables, or doing minor restructuring based on what it knows about the interpreter.

I suppose, back in the 80s, processors were so slow and memory so limited that doing such an optimizer might not be practical. But today, imagine how you could optimize a .BAS file on a modern computer (just like we have 6809 cross-hosted compilers that compile and link in the link of an eye).

Maybe this has already been done for some other flavor of BASIC? Hmmm. I may have to do some Googling.

A tale of two word wrap routines

  • 2015/1/4 Update: After reading this, you can check out the next installment, too.
  • 2015/1/5 Update: The BASIC listings should be fixed now.

In a recent article, I demonstrated a simple way to do a word wrap output routine in Extended Color BASIC on a Radio Shack Color Computer. When I went to try to use the routine, I found a few issues with it that I would like to address here.

My first version was able to word wrap all the way to the last character on a line, but I failed to test against a string that was longer than the screen width (with no spaces in it). This would only happen if you had a very long word on a very narrow screen (supercalifragilisticexpialidocious would be a word too long to fit on a 32-column CoCo screen, for example). I fixed this issue, and also optimized the routine to handle short strings better by checking for them at the start instead of the end.

I now have three test cases to check for: short strings that do not need to wrap, long strings that do need to wrap (with a word ending on the final 32nd column), and strings with word(s) longer than the screen width.

The first version I presented tried to avoid using any additional strings. Simply doing something like A$=A$+”BACON” causes a new larger string buffer to be allocated, the original data copied over, new data appended, and original string assigned to it and the old one released. I wanted to avoid that, so my routine worked just by printing out sections of the original string.

The end result was a routing that worked, but due to the nature of BASIC, was quite slow. Text did not just appear – it paused a bit before printing each line. In my test program, this was a bit annoying, so I decided to see if I could make it a bit faster.

While it’s true that the CPU overhead of doing string manipulation is significant, much more CPU time is spent processing tokenized BASIC. If you could save some BASIC time by doing string manipulation, you should come out ahead with a faster problem.

First, here is the test I am using for both my word wrap routines:

10 CLEAR200
20 CLS
30 INPUT"SCREEN WIDTH [32]";WD
40 IF WD=0 THEN WD=32
50 INPUT"UPPERCASE ([0]=NO, 1=YES)";UC
60 TIMER=0:TM=TIMER
70 PRINT "SHORT STRING:"
80 A$="This should not need to wrap.":GOSUB 1000
90 PRINT "LONG STRING:"
100 A$="This is a string we want to word wrap. I wonder if I can make something that will wrap like I think it should?":GOSUB 1000
110 PRINT "WORD > WIDTH:"
120 A$="123456789012345678901234567890123 THAT WAS TOO LONG TO FIT BUT THIS IS EVEN LONGER ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234 SO THERE.":GOSUB 1000
130 PRINT"TIME TAKEN:"TIMER-TM
140 END

And this is the updated original word wrap routine that does not allocate any extra strings. I have also renamed the variables in my subroutines, starting all the temporary variables with Z (ZS for start of string, ZE for end of string, etc.). I did this to avoid conflicts with my larger program.

1000 REM WORD WRAP V1
1010 '
1020 'IN : A$=MESSAGE
1030 ' UC=1 UPPERCASE
1040 ' WD=SCREEN WIDTH
1050 'OUT: LN=LINES PRINTED
1060 'MOD: ZS, ZE, ZP
1070 '
1080 LN=1
1090 IF A$="" THEN PRINT:RETURN ELSE ZS=1
1100 IF UC>0 THEN FOR ZP=1 TO LEN(A$):ZP=ASC(MID$(A$,ZP,1)):IF ZP<96 THEN NEXT ELSE MID$(A$,ZP,1)=CHR$(ZP-32):NEXT
1110 ZE=LEN(A$)
1120 IF ZE-ZS+1<=WD THEN PRINT MID$(A$,ZS,ZE-ZS+1);:IF ZE-ZS+1<WD THEN PRINT:RETURN
1130 FOR ZE=ZS+WD TO ZS STEP-1:IF MID$(A$,ZE,1)<>" " THEN NEXT:ZP=0 ELSE ZE=ZE-1:ZP=1
1140 IF ZE<ZS THEN ZE=ZS+WD-1
1150 PRINT MID$(A$,ZS,ZE-ZS+1);
1160 IF ZE-ZS+1<WD THEN PRINT
1170 LN=LN+1
1180 ZS=ZE+1+ZP
1190 GOTO 1110
CoCo_wordwrp1_mem
Size of BASIC word wrap program version 1.

When I start up a 32K (or larger) CoCo 1/2, PRINT MEM shows 22823 bytes available (some memory is reserved for graphics screens, so there are ways to get even more memory to BASIC if you aren’t doing graphics).

After loading the program, I check memory again and determined this version took 1101 bytes.

CoCo_wordwrp1_time
BASIC word wrap program version 1 output.

When I run the program, it handles all the test cases as I expected, and reports that the time taken as 113 (counts of the TIMER, with each number being based on the screen interrupt – 60hz on the US machines. Is it different on PAL machines that run at 50mhz?)

This gives me an idea of large the program is and how fast it is to run.

I then took this version and converted it (keeping the lines the same as much as possible) to one that used LEFT$() and RIGHT$() to chop the input string up as it went, making the code simpler at the cost of pushing more work on the BASIC string manipulation routines. It looked like this:

1000 REM WORD WRAP V2
1010 '
1020 'IN : A$=MESSAGE
1030 ' UC=1 UPPERCASE
1040 ' WD=SCREEN WIDTH
1050 'OUT: LN=LINES PRINTED
1060 'MOD: ST, EN
1070 '
1080 LN=1
1090 IF A$="" THEN PRINT:RETURN
1100 IF UC>0 THEN FOR ST=1 TO LEN(A$):EN=ASC(MID$(A$,ST,1)):IF EN<96 THEN NEXT ELSE MID$(A$,ST,1)=CHR$(EN-32):NEXT
1110 ZE=LEN(A$)
1120 IF ZE<=WD THEN PRINT A$;:IF ZE<WD THEN PRINT:RETURN
1130 FOR ZE=WD+1 TO 1 STEP-1:IF MID$(A$,ZE,1)<>" " THEN NEXT:ZP=0 ELSE ZE=ZE-1:ZP=1
1140 IF ZE=0 THEN ZE=WD
1150 PRINT LEFT$(A$,ZE);
1160 IF ZE<WD THEN PRINT
1170 LN=LN+1
1180 A$=RIGHT$(A$,LEN(A$)-ZE-ZP)
1190 GOTO 1110
CoCo_wordwrp2_mem
Size of BASIC word wrap program version 2.

When I load this version and check memory, it gives me 1058 — 43 bytes less than the first version. This also means there will be less bytes for the interpreter to have to process so it might run faster, too.

CoCo_wordwrp2_os
BASIC word wrap program version 2 out of string space error.

However, when I tried to run it, the program crashed with an Out of String Space error (?OS ERROR). Even though the BASIC program was smaller, it was now needing to allocate additional/temporary strings to do things like A$=LEFT$(A$,xxx). By default, Color BASIC sets aside 200 bytes for strings, and that was not enough for this version.

CoCo_wordwrp2_time
BASIC word wrap program version 2 output.

To fix this, I had to change the “CLEAR 200” on line 10 to be something larger, like “CLEAR 300”. That was enough for the program to run and I saw it’s time used was around 106. This was only slightly faster than the method that did not require extra string space so it really would only be useful if you needed that teeny tiny speed improvement and had the extra memory to spare.

Now, if the extra string space required was no more than the size saved by the smaller code, it would end up being an advantage. For my program, I do not expect memory to be an issue (it will be a fairly small program) so I may very well end up optimizing as many functions as I can for speed over memory.

Speaking of optimizations, coming up soon will be some notes on easy ways you can optimize BASIC to make your program take up less memory and/or run faster.

Until then, I’ll see you at the “OK” prompt…

Word wrapping in Extended Color BASIC

2015/1/2 Update: Please see my follow-up article for two improved versions of this.

Last night, I began writing a program on my 1980s Radio Shack Color Computer. I am planning on going through hundreds of old floppy diskettes and copying them to disk image files.  The program I began writing will help automate this task.

I am using a new bit of retro hardware called the CoCoSDC. It acts like a floppy drive controller, but instead of writing to round pieces of magnetic plastic, it writes that data out to disk image files on an SD card. You can learn more about CoCoSDC in another series of articles I have been writing.

CoCoSDC is a brilliant piece of design work in that it detects and adjusts for the various models of Color Computers (1, 2 or 3) it is plugged in to. I decided my program should do the same, so my goal is to have one BASIC program that could be loaded on an original 1980 Color Computer 1 or 2, using its 32 column display, or run on a 1986 Color Computer 3, using the 40 or 80 column text screen. There are a few challenges I needed to solve:

  • The program should work on an original Radio Shack TRS-80 Color Computers or early model TRS-80 CoCo 2s, which had a 32 column display with uppercase character set. (On the Motorola 6847 VDG chip used by this machine, lowercase letters were represented by inverse uppercase characters.)
  • The program should take advantage of the later model Tandy Color Computer 2s that had an enhanced VDG chip that could display true lowercase. This option should still be available for earlier model CoCos which may have an aftermarket lowercase kit (like my old grey CoCo 1 has).
  • The program should work on a Tandy Color Computer 3, and let the user select between 32, 40 or 80 column display. This would allow it to worked hooked up to a crappy old TV display, or have nice 80 column text for users that had a good monochrome composite monitor or RGB-A color monitor.

I quickly designed and wrote a functional prototype, missing only a few features I had in mind. I started out by hard coding it to use a variable for the screen width so I could center text and and such. For example:

WD = 32
A$ = "CENTER THIS!"
PRINT TAB(WD/2-LEN(A$)/2);A$

By making that PRINT line a subroutine, I could call it anywhere I wanted to print centered text:

10 WD = 32 'SET SCREEN WIDTH
20 CLS 'CLEAR SCREEN
30 A$="IT WORKS!":GOSUB 1000
40 END
1000 REM PRINT CENTERED A$
1010 PRINT TAB(WD/2-LEN(A$)/2);A$
1020 RETURN
CoCo centered text.
XRoar emulator acting as a Radio Shack Color Computer 1.

I also made a routine to display a horizontal row of dashes so I could use that with nicely formatted menus.

Eventually, I needed to display some verbose text to the user, and knew I would need to make a routine to handle wrapping long lines. It’s easy to hard code PRINT statements when you know your screen width, but when it can vary, we can have the program do it for us.

Earlier in 2014 I implemented such a word wrap routine in C for a Raspberry Pi program I was designing. Unfortunately, porting that code to BASIC wouldn’t be possible since it took advantage of too many C features that have no BASIC equivalents. Instead, I had to design a new one.

Also, I wanted the option to pass in strings with upper and lower case and have them converted to uppercase. The only design restriction I enforced was to try not to do any string rebuilding. Interpreted BASICs have their own type of garbage collection (like you might find in Java and other modern languages) that do all kinds of memory shifting when you add two strings together. My program could be faster if it avoided that. (Not that it really matters, but good habits learned later in my programming career are difficult to break even when I am pretending it’s 1983.)

Here is the first version I came up with:

2015/1/5 Update: Please see my next article for two improved versions of this.

10 CLS 15 INPUT"SCREEN WIDTH [32]";WD:IF WD=0 THEN WD=32
20 INPUT"UPPERCASE ([0]=NO, 1=YES)";UC
25 TM=TIMER
30 A$="This is a string we want to word wrap. I wonder if I can make something that will wrap like I think it should?":GOSUB 1000
35 PRINT"TIME TAKEN:"TIMER-TM
999 END
1000 REM WORD WRAP
1001 '
1002 'IN : A$=MESSAGE
1003 '     UC=1 UPPERCASE
1004 '     WD=SCREEN WIDTH
1005 'OUT: LN=LINES PRINTED
1006 'MOD: ST, EN
1007 '
1010 IF A$="" THEN PRINT:LN=1:RETURN
1015 IF UC>0 THEN FOR ST=1 TO LEN(A$):EN=ASC(MID$(A$,ST,1)):IF EN<96 THEN NEXT ST ELSE MID$(A$,ST,1)=CHR$(EN-32):NEXT ST
1020 LN=0:ST=1
1025 EN=LEN(A$)
1030 IF MID$(A$,ST,1)=" " AND ST<EN THEN ST=ST+1:GOTO 1030
1035 IF EN-ST>WD THEN FOR EN=ST+WD TO ST STEP-1:IF MID$(A$,EN,1)<>" " THEN NEXT EN ELSE EN=EN-1
1040 IF EN=ST THEN EN=ST+WD-1
1045 PRINT MID$(A$,ST,EN-ST+1);
1050 LN=LN+1
1055 IF EN-ST+1<WD THEN PRINT
1060 ST=EN+1:IF ST<LEN(A$) THEN 1025
1065 RETURN

Note: This is not my normal production programming style. I created this example for better readability, but in the future I will share some of the tricks we used at Sub-Etha to optimize BASIC programs to take up less memory and run faster. (They won’t be very readable…)

Word wrap, lowercase.
Word wrap, lowercase.
Word wrap, uppercase.
Word wrap, uppercase.

The subroutine at line 1000 expects the string to be in A$, the width of the display in WD, and the variable UC can be set to 1 to force the output to be in uppercase (which is slower). When it returns, LN will contain how many lines were printed. I also print out the TIMER so I can compare the speed of the routine, and see how much slower it is with uppercase conversion.

On the right are screen shots of the wrapped output with and without uppercase conversion. You can see that converting a few lines of text the way I did it took about five times longer.

There is one extra feature I should mention. One of the things that bothers me about many word-wrap routines I have seen is that they tend to ignore the final character of a line meaning that if you print a line that is exactly 40 characters long to a 40 column display, they usually wrap the last word even though it would have fit on the line. This is because, at least on all the systems I have experience with, when you print to the final character, the cursor moves to the start of the next line, then the PRINT command finishes and adds a carriage return. Here is an example of printing three 32 column lines on the CoCo:

10 CLS
20 PRINT"12345678901234567890123456789012"
30 PRINT"12345678901234567890123456789012"
40 PRINT"12345678901234567890123456789012"

You get something like this:

Printing without semicolon.
Printing without semicolon.

BASIC does not know that a line was skipped before it adds its own carriage return. You can prevent BASIC from adding a carriage return by adding a semicolon to the end of the PRINT line:

10 CLS
20 PRINT"12345678901234567890123456789012";
30 PRINT"12345678901234567890123456789012";
40 PRINT"12345678901234567890123456789012";
Printing with semicolon.
Printing with semicolon.

Most word wrap routines I have seen just don’t bother dealing with this, and never use the final character of the line. For my routine, I wanted a line that was exactly the length of the screen width to fit, so I always add the semicolon, then in line 1055 I print a carriage return if the line was shorter than the screen width (and thus BASIC didn’t already move the cursor to the next line).

Little things like this make the code bulkier than it needs to be.

Now I turn things over to you… This can be done much better. How would you implement a word wrap? And note there are several goals that would require different code:

  • Code size may be most important.
  • Execution speed may be most important.
  • You might want to use as few variables as possible.
  • You might want to avoid string manipulation.
  • You might not be able to alter any variables passed in (thus, if the user passes you A$, you are not allowed to change A$).

For me, I wanted to avoid string manipulation (for speed) and use fewer variables. Without that goal, I could have done the word wrap routine easier by making a copy of the string the user passed in and manipulating the copy. This is why my uppercase conversion makes changes to the string the user passed in. If I had the goal of not modifying what the user passed in, I would have had no choice other than making a copy in another string variable.

The choice is yours. Send in your best attempt and explain your goal. Here are the requirements:

  • A$ will be set to the string the user wishes to display.
  • WD will be set to the screen width.
  • UC will be 0 if the string is to be displayed as-is, or not 0 to convert to uppercase.
  • On return, LN will be the count of how many lines were displayed.

If you need a quick and easy emulator to run it in, check out XRoar. I have tips on how to get it running over at CoCopedia:

http://www.cocopedia.com/wiki/index.php/Using_XRoar

Have fun!

P.S. This example requires Extended Color BASIC. As it turns out, while the 1980 Color BASIC supports MID$(), you cannot use it to modify a string. Thus, A$=MID$(B$,1,1) would work, but MID$(B$,1,1)=”N” would not. My first CoCo had Extended Color BASIC so I never had to live without the extra features.

Program like it’s 1980!

Just for fun, let’s pretend it is the summer of 1980, and you just walked in to a Radio Shack and saw the brand new TRS-80 Color Computer. Unlike the original TRS-80 Model I, this thing could hook up to a color television (instead of a monitor) and display colors and make sound! Amazing.

If you picked one up (and a cable to hook up a cassette recorder for loading and saving programs), what would you do with it? I propose a fun challenge to find out.

Rules:

Your program can use any modern knowledge, but must run on a stock 1980 4K CoCo running Color BASIC 1.0.

That’s it. However, certain things would not be possible to create ON that machine. For instance, the EDTASM assembler ROM Pak required at least 16K, so any assembly language written on a 4K CoCo had to be hand assembled (somehow). If someone actually does that, it should be noted and given special consideration.

I propose the entries will be created in one of the following ways:

Native versus Expanded versus Cross Hosted – a program could be written on an actual 4K CoCo (native), or on a more expanded CoCo (16K CoCo 1, 512K CoCo 3, etc.), or compiled using PC/Mac/Linux cross compiler tools.

Real versus Emulated – likewise, the coding could be done on a real CoCo, or a virtual one in an emulator.

Ultimately, doing it actually on a native 4K real CoCo would be the only way it could have been done in 1980, but if someone wants to participate using an emulated one that is fine (but it will be noted, just so we can congratulate someone for actually still having a 4K CoCo around that never got upgraded).

More impressive things could probably be done using a later environment (EDTASM on a larger CoCo rather that native hand compiling), or using PC tools. That’s a different type of development, but ultimately, all should run on a stock 4K CoCo.

If you might want to follow this as we figure out how to approach it (or participate), details will be at the CoCoPedia wiki. In coming weeks I will make updates as we figure out more to this challenge:

http://www.cocopedia.com/wiki/index.php/CoCoCoding_1980_Contest