- 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 ";WD 40 IF WD=0 THEN WD=32 50 INPUT"UPPERCASE (=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
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.
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
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.
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.
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…