BASIC auto-numbering and labels
While there are lots of things I dislike about writing in old-school BASIC, number one on the list is probably having to deal with cross-referencing line numbers. It is always annoying to have to add new code, renumber the existing lines, and then make sure that any cross-references have also been fixed.
So, of course, I wrote a Perl script to help me write BASIC programs (Zip file, 3.0 KB).
This allows me to write BASIC programs without line numbers, placing labels where I need them. Some of this is what I vaguely remember from programming in BASIC09 on OS-9 on the Tandy Color Computer.
Here’s a simple program to calculate the day of the week for any date since 1752:
Original | With automatic line numbers |
---|---|
|
|
The script converted the labels for CALCULATE and for GETDATE into line numbers in the references at lines 140 and 160. It also skipped the second and third sections to line 200 and 300, recognizing that an empty line meant a new section.
Here’s a simpler script with more sections in it, modified from the DWP 230 manual.1
Original | With automatic line numbers |
---|---|
[toggle code]
|
[toggle code]
|
You can see that it moved the blank-line sections up to the nearest hundred, and the pound-sign section up to the nearest thousand. It also incremented the data statements by 1 instead of the default 10. In this case that’s useful because it helps me ensure I have the correct number of DATA statements. Each DATA statement corresponds to a daisy wheel position, and there are 96 of them (0 to 95, in the numbering of this BASIC program).
Also helping to ensure correct data lines, the #DATA directive takes all succeeding lines and puts them into data statements whose size (unless one element is greater than about 30 characters long) does not exceed 40 characters per line. #ENDDATA ends the data collection, potentially with a final data statement, such as “DATA end”. This makes it easy to keep DATA elements in order, such as alphabetical order, using sorting services on macOS.
Original | With automatic line numbers |
---|---|
|
|
DATA statements are assumed to jump to the next thousandth line.
Directive | Result |
---|---|
#LABEL:text [remark] | store this label for use in GOTO, GOSUB, and THEN statements, optionally using extended text as a REM statement |
blank line | round next line up to the nearest hundred |
# text | round next line up to the nearest thousand, and use text as a REM statement |
## text | round next line up to the nearest 500th, and use text as a REM statement |
#INCREMENT:number | increment succeeding lines by this amount |
#DATA [remark] | slurp the next lines (until end or #ENDDATA) into 40-character DATA statements |
#ENDDATA [final datum] | end data slurping, optionally with a final data line |
There is a default label of THISLINE that just goes back to the current line. For example, in While sorrowful dogs brood: The TRS-80 Model 100 Poet the program reads the data in to count how many nouns, feelings, verbs, and adjectives/adverbs it has:
Original | With automatic line numbers |
---|---|
|
|
When matching cross-references, it makes a half-hearted attempt to find misspellings: if anything other than a number follows a GOTO or GOSUB after the line has had its cross-references replaced, the script will abort with an error message. This does not catch errors after THEN, after ELSE, or after commas (such as in an ON GOTO or ON GOSUB statement). Also, if your BASIC allows storing line numbers in variables, you’ll probably want to remove this check.
The script also currently only replaces cross-references following GOTO, GOSUB, THEN, ELSE, as well as commas following a GOTO or GOSUB (for ON x GOTO/GOSUB…). As I use the script more often, I’ll probably find more places where line numbers are allowed and thus where I need to catch and replace labels.
[toggle code]
- #!/usr/bin/perl
- #convert source file into BASIC program lines with line numbers
- use POSIX;
- $return = "\r\n";
- #width of Model 100 display
- #currently used only for automatic DATA lines
- $screenWidth = 40;
-
if ($ARGV[0] =~ /^--return$/ && $ARGV[1] =~ /^(cr|lf|crlf)$/) {
- shift @ARGV;
- $return = shift @ARGV;
-
if ($return eq 'cr') {
- $return = "\r";
-
} elsif ($return eq 'lf') {
- $return = "\n";
-
} elsif ($return eq 'crlf') {
- $return = "\r\n";
- }
- }
- #compile DATA lines
-
sub compileData {
- return if $#data == -1;
- local($dataLine) = '';
-
for $data (@data) {
-
if (length($dataLine) + length($data) > ($screenWidth-(6+length($line)))) {
- makeDataLine();
- }
- $dataLine .= ', ' if $dataLine;
- $dataLine .= $data;
-
if (length($dataLine) + length($data) > ($screenWidth-(6+length($line)))) {
- }
- makeDataLine() if $dataLine;
- undef(@data);
- }
-
sub makeDataLine {
- $lines[$#lines+1] = "$line DATA $dataLine";
- $dataLine = '';
- $line += $increment;
- }
-
sub sectionBreak {
- my($break, $remark) = @_;
- $lines[$#lines+1] = "" if $lines[$#lines] ne "";
- $line = ceil($line/$break)*$break;
-
if ($remark) {
- $line -= 1 if $increment >= 5;
- $lines[$#lines+1] = "$line REM $remark";
- $line += 1 if $increment >= 5;
- }
- }
- $line = 10;
- $increment = 10;
-
while (<>) {
- chomp;
-
if (m/^$/) {
- sectionBreak(100);
-
} elsif (m/^#INCREMENT:([0-9]+)$/) {
- $increment = $1;
-
} elsif (m/^#ENDDATA( (.*))?$/) {
- $finalDatum = $2;
-
if (!$inData) {
- print STDERR "#ENDDATA without #DATA\n";
- die;
- }
- $inData = 0;
- compileData();
-
if ($finalDatum) {
- $lines[$#lines+1] = "$line DATA $finalDatum";
- $line += $increment;
- }
-
} elsif ($inData) {
- $data[$#data+1] = $_;
-
} elsif (m/^#LABEL:([a-zA-Z][a-zA-Z0-9_]+)(.*)/) {
- $labels{$1} = $line;
-
if ($2) {
- $remark = $2;
- $remark =~ s/^\s+|\s+$//g;
-
} else {
- $remark = lc($1);
- $remark =~ s/_/ /g;
- }
- $lines[$#lines+1] = "$line REM $remark";
- $line += $increment;
-
} elsif (m/^##? (.+)$/) {
- $remark = $1;
- $break = 1000;
- $break = 500 if m/^## /;
- sectionBreak($break, $remark);
-
} elsif (m/^#DATA (.*)$/) {
-
if ($remark = $1) {
- sectionBreak(1000, $remark)
- }
- $inData = 1;
-
if ($remark = $1) {
-
} else {
- $lines[$#lines+1] = "$line $_";
- $line += $increment;
- }
- }
- compileData() if $inData;
-
foreach $line (@lines) {
- $line =~ m/^([1-9][0-9]*) /;
- $labels{'THISLINE'} = $1;
- $labelNames = join('\b|', keys %labels) . '\b';
- #replace simple gosubs, gotos
- $line =~ s/(GOSUB|GOTO|THEN|ELSE)( ?)($labelNames)/$1$2$labels{$3}/g;
- #replace ON X GOSUB, ON X GOTO
- while ($line =~ s/(GOSUB|GOTO)([ ,0-9]*)($labelNames)/$1$2$labels{$3}/g){}
- #If there remain non-numeric characters after a GOSUB or a GOTO, that's an error on the Model 100/200
-
if ($line =~ m/(GOSUB|GOTO)[ ]*([^0-9 :]+)/) {
- print STDERR "NON-EXISTENT LABEL $2 ON LINE $line\n";
- print STDERR join(', ', keys %labels), "\n";
- die;
- }
- print "$line$return";
- }
As you can see, the script is a simple one. It loops through each line looking for directives and saving up each line, giving each line its number. Then it loops again, looking for cross-references and printing each line after hopefully replacing all cross-references.
There is also a command-line switch, --return, for changing the line endings between the default CRLF to either CR or LF. If the old-school computer you’re using this on has a different line ending, you’ll probably just want to change the default at the top of the script.
Getting rid of line numbers in the source files makes it a lot easier to use version control on my BASIC programs. Before, any change in line number would trigger a change detection, even if the only thing that changed was the line number, in order to make room for something else. This tended to obscure the real code change, making version control much less useful for old-school BASIC programs. By using this script, only actual changes in code are displayed in Mercurial2, making the real change much easier to see.
Another advantage to this script is that it accepts multiple files on the command line. For the daisy wheel data selector I’m working on, I have one BASIC file with the code, and several with wheel data from the manual, as well as one I’m trying to get working. To generate the BASIC program for a specific wheel, I specify the code file and then the data file.
- ~/bin/basicize ibmwheel.bas diablo.bas > ~/Model100/diablo.ba
While writing BASIC programs in a text editor on a non-BASIC workstation means foregoing the interactivity, it is still a lot easier to write programs in full-screen text editors such as Textastic. And with modern drag and drop and emulators, it’s still very easy to test out many of the BASIC programs I’m writing for old-school computers, using emulators such as VirtualT.
Note that it doesn’t actually work yet, as far as I can tell. It doesn’t generate any errors, but it also does not behave as expected.
↑I use SourceTree on the Mac.
↑
- BASIC auto-number script (Zip file, 3.0 KB)
- Automatically number BASIC lines and use labels instead of line numbers in GOTO, GOSUB, etc.
- Sourcetree
- “Sourcetree simplifies how you interact with your Git repositories so you can focus on coding. Visualize and manage your repositories through Sourcetree's simple Git GUI.” (Also your Mercurial repositories.)
- Textastic: Alexander Blach
- This is a “powerful and fast text editor” both for iPad and Mac OS X. The iPad version has built-in SFTP support making it very useful for editing files on remote servers.
- VirtualT
- An emulator for the TRS-80 Model 100, 102, and 200.
- While sorrowful dogs brood: The TRS-80 Model 100 Poet
- Random poetry: a BASIC random primer and a READ/DATA/STRINGS primer, for the TRS-80 Model 100.
More BASIC
- Simple game menu for the Color Computer 2 with CoCoSDC
- This simple menu provides one screen for cartridges saved in the CoCoSDC’s flash ROM, and any number of screens for your favorite games for your friends to play.
- BASIC tokenization examined
- Why do old BASIC programs have strange characters in their .BAS files? Why do they look like they’re compiled code?
- Read BASIC out loud
- Reading BASIC out loud is a great tool for verifying that what you’ve typed in from an old-school magazine or book is correct.
- Convert PCBASIC code to TRS-80 Extended Color BASIC
- If you have a book of code written in PCBASIC, it usually isn’t hard to convert it to other Microsoft BASICs, such as on the TRS-80 Color Computers.
- SuperBASIC for the TRS-80 Color Computer
- Make BASIC Fun Again. Use loops, switches, and subroutines while writing Extended Color BASIC code for the Radio Shack Color Computer.
- Six more pages with the topic BASIC, and other related pages