Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, Swift, BASIC, and whatever else I happen to feel like hacking at.

Rainbow Magazine BASIC program preflight tool

Jerry Stratton, January 8, 2020

Vicious Vic by Jay R. Hoggins: The title screen for Jay R. Hoggins’s Vicious Vic. From the July 1986 Rainbow Magazine.; Color Computer; CoCo, TRS-80 Color Computer; The Rainbow magazine

Vicious Vic appears to be a variation on Robot War, with vikings in place of robots.

There are a lot of typing mistakes I make that are easily detectable: extra characters from fat fingers, mistyped parentheses, and even mistyped numbers. In an attempt to make typing BASIC programs from The Rainbow easier, as well as improve my ability to use Textastic with rcheck, I wrote this script. I wrote it somewhat haphazardly, adding new checks when I noticed that an error was detectable.

The basic idea is that since each line wraps at 32 characters, when I hit the end of the line in the Rainbow listing, I hit return. The script detects whether a line in the text file is a continuation of the previous line or a new line by looking for the line number. This solves the problem of finding a text editor that wraps at 32 characters.

But it also opens the possibility of doing some preflighting. If a line in the text file is not the final subline in a BASIC code line, it must be 32 characters long. If it isn’t, I mistyped something. And with my fat fingers, this is the most common typo. Most typos are discovered merely by making sure that each subline except the final one is exactly 32 characters.

There are also warnings, things that are probably a problem but might not be. These do not halt the program, and are displayed at the bottom of the results. They are not piped to a file, so they appear in the terminal if you’re piping.

For example, if the full BASIC line is longer than 249 characters, I probably mistyped something. That’s the maximum number of characters you can type on a new BASIC line on the Color Computer. It’s a warning, not an error, because it is possible to get more characters into a line, and it occasionally happens with the one-line and two-line contest winners, since they’re specifically trying to stuff as much into a line as possible. More than 255 characters is an error. I haven’t seen anything longer than that.1

It also checks the line number. If it isn’t greater than the previous line number, I mistyped something.2

And it checks for other things that I’ve noticed are detectable. If there aren’t the same number of close parentheses as open parentheses in the full BASIC line, that’s a problem.3 It looks for mistyped and missing colons, where possible.

I also have a special option for programs that are mainly just DATA statements POKEing code into memory. Such lines should only contain specific characters, the numbers 0-9 and the letters A-F, as well as the comma or space. The option --MLDATA checks all data lines and looks for characters outside of those limits. This is for programs like Rooter in the May, 1986 Rainbow, or Vicious Vic in the July 1986 Rainbow. The former uses BASIC to create a binary file for saving; the latter uses DATA statements for the title screen.

You can also use the --warnings option to suppress all output except errors and warnings.

The script ignores blank lines, which means I can hit return twice at the end of each column on a page, to make it easier to match between the page and the screen. It also ignores lines that begin with a pound symbol at the top of a file, which allows for comments at the top about where this BASIC program came from, as well as the list of checkpoints for running against rcheck.

[toggle code]

  • #!/usr/bin/perl
  • # facilitate typing code from The Rainbow magazine by
  • # unwrapping 32-character lines to longer BASIC lines
  • # and attempting to do basic error checking along the way
  • # Jerry Stratton hoboes.com/coco/
  • #command-line arguments
  • while ($option = shift @ARGV) {
    • if ($option =~ /^--MLDATA$/i) {
      • $machineData = 1;
    • } elsif ($option =~ /^--help/i) {
      • help();
    • } elsif ($option =~ /^--warnings/i) {
      • $warnings = 1;
    • } else {
      • $files[$#files+1] = $option;
    • }
  • }
  • @ARGV = @files;
  • #loop through BASIC code
  • while (<>) {
    • chomp;
    • #ignore blank lines
    • next if /^$/;
    • #ignore top comments
    • next if /^#/ && !defined($previousLineNumber);
    • #if this is a new BASIC line, get the line number and the code in that line
    • ($lineNumber, $lineCode) = ($1, $2) if /^([0-9]+) (.*)$/;
    • #if this is a new BASIC line, handle the old one and start the new one
    • #note it is possible for a 33rd character to be a number followed by a space
    • #and if the number is greater than the current line number, the script will
    • #incorrectly detect a new line
    • if ($lineNumber > $previousLineNumber || (!defined($previousLineNumber) && $lineNumber eq "0")) {
      • print "\n" if $outputStarted;
      • #errors or warnings on a full BASIC line basis
      • #check that line is not too long
      • #249 is the maximum that can be typed in initially;
      • #but it is possible to get more into a line by editing it
      • #longest so far: 255 characters
      • die("\nLINE $previousLineNumber WAS $lineLength CHARACTERS LONG") if $lineLength > 255;
      • #check for mismatched parentheses
      • $openParenCount = () = $lineText =~ /\Q(/g;
      • $closeParenCount = () = $lineText =~ /\Q)/g;
      • die("\nLINE $previousLineNumber HAS MISMATCHED PARENTHESES") if $openParenCount != $closeParenCount;
      • #commas, semicolons, or quotes instead of colons
      • die("\nLINE $previousLineNumber mistyped colon: $lineText") if $lineText =~ /[,;'"][A-Z][A-Z0-9]?=/;
      • #missing colon
      • die("\nLINE $previousLineNumber missing colon: $lineText") if $lineText =~ /[0-9][A-Z][A-Z0-9]?\$?=/;
      • #warnings, but not critical
      • addWarning($previousLineNumber, "has more than 249 characters ($lineLength)") if $lineLength > 249;
      • addWarning($previousLineNumber, "= should be -?: $lineText") if $lineText =~ /(PUT|LINE)\([^)]+\)=\(/;
      • $lineLength = 0;
      • $previousLineNumber = $lineNumber;
      • $lineText = '';
      • #is this a standalone DATA line?
      • if ($lineCode =~ /^DATA(.*)$/) {
        • $inData = 1;
        • $dataElements = $1;
      • } else {
        • $inData = 0;
      • }
    • } else {
      • #has the line number gone down or stayed the same?
      • if ($lineNumber && $lineNumber <= $previousLineNumber) {
        • #if the previous line was exactly 32 characters long,
        • #assume this is not actually a new line number but rather the
        • #continuation of the previous one
        • if ($previousLength != 32 || $lineCode =~ /^DATA /) {
          • die("\nLINE $lineNumber COMES AFTER LINE $previousLineNumber: $_\n");
        • }
      • }
      • #sublines should be 32 characters
      • if ($previousLength != 32) {
        • print "\n" if $outputStarted;
        • die("\nSUBLINE WAS NOT 32 CHARACTERS LONG in $previousLineNumber\n");
      • }
      • $dataElements = $_ if $inData;
    • }
    • #print the line unless looking for warnings only
    • if (!$warnings) {
      • print;
      • $outputStarted = 1;
    • }
    • $lineText .= $_;
    • #these are for errors or warnings on a per-subline basis
    • #no subline should be more than 32 characters long
    • die("\nSUBLINE $_ LONGER THAN 32 CHARACTERS in $previousLineNumber\n") if length > 32;
    • #if DATA elements are machine language codes, all characters should be hex or dec
    • die("\nNON-HEX CHARACTER IN DATA SUBLINE $previousLineNumber: $_\n") if $machineData && $inData && $dataElements =~ /[^, 0-9A-F]/i;
    • $previousLength = length;
    • $lineLength += $previousLength;
    • $lineNumber = '';
  • }
  • print "\n";
  • if (@warnings) {
    • print STDERR "\nWARNING:\n";
    • for $warning (@warnings) {
      • print STDERR "\t$warning\n";
    • }
  • }
  • sub help {
    • print "$0 [--mldata] [--help] [--warnings] [filenames]\n";
    • print "merge 32-character RAINBOW lines into BASIC program, performing rudimentary error checking.\n";
    • print "\t--mldata: assume that DATA lines all contain machine code\n";
    • print "\t--help: print this help\n";
    • print "\t--warnings: do not print code, only print warnings and errors\n";
    • exit;
  • }
  • sub addWarning {
    • my $lineNumber = shift;
    • my $warning = shift;
    • $warnings[$#warnings+1] = "LINE $lineNumber: $warning";
  • }

I’ve run this script on several BASIC programs from Rainbow’s 1986 issues; if you want to add (or remove) checks, it’s easy to do so. Errors use die and warnings use addWarning. You can also switch them around if what I have as an error ought to be a warning in the programs you’re typing.

This script is easily used with rcheck. Run this script first, piping to a file, and then run rcheck on the file:

[toggle code]

  • ~/bin/rainbow --MLDATA vicious.txt  > VIC.BAS
  • ~/bin/rcheck VIC.BAS 40 154 158 161 165 169 243 250 400 610 672 730 840 1070 1190 1350 1510 1680 1930 2200 2360 2520 2670 2769

Which since as I write this I am not yet finished typing Vicious Vic in, nets me:


I tend to type Rainbow code now a column at a time, and check the various checkpoints at the end of the column, or even at the end of a page. This script makes typing code from Rainbow a lot faster, if I can restrain the temptation to look at the screen when I know I just made a mistake.

In response to TRS-80 Color Computer Programming Tools: The TRS-80 Color Computer was a fascinating implementation of the 6809 computer chip, and was, from the Color Computer 1 through 3, possibly the longest-running of the old-school personal computers.

May 4, 2022: Rainbow Magazine preflight tool enhanced
The Dragon’s Gold: The start of play in Charles Husak’s Color Computer game “The Dragon’s Gold”, from the August 1984 Rainbow Magazine.; Color Computer; CoCo, TRS-80 Color Computer; The Rainbow magazine; retro computer games; 8-bit computer games

I’ve been silently updating the Rainbow preflight tool (Zip file, 3.2 KB) over the last couple of years, but I just added a feature that I thought worth talking about. I was typing in Charles Husak’s “Quest of the Dragon’s Gold” from the August 1984 Rainbow and the line numbers are all over the place. But between pages 32 and 36 the line number jumps from 2790 to 2840, which seemed excessively odd even for this particular program.

I did a search on the obvious line numbers that might exist to see if there were any GOTOs, GOSUBs, or so on, to the potentially missing section, and didn’t find any. But given how uneven the line numbers were, that wasn’t a great check. It occurred to me that I could have the rainbow script keep track of (a) all of the line numbers in the listing, and (b) all of the line numbers referenced in the code, and then cross reference them.

This was fairly simple to do: there’s a subroutine to collect the line numbers, and a loop that provides a warning for every line number that doesn’t exist. Here’s the subroutine:

[toggle code]

  • sub collectLineReferences {
    • my $lineNumber = shift;
    • my $lineText = shift;
    • $existingLines[$#existingLines+1] = $lineNumber;
    • while ($lineText =~ m/(GO *TO|GO *SUB|THEN) *([0-9]+)/g) {
      • $lineReferences[$#lineReferences+1] = [$lineNumber, $1, $2];
    • }
  • }

And here are the warnings:

[toggle code]

  • #verify that all line number references go to a line that exists
  • %existingLines = map { $_ => 1 } @existingLines;
  • foreach $lineReference (@lineReferences) {
    • ($lineNumber, $referenceType, $referencedLine) = @$lineReference;
    • if (!exists($existingLines{$referencedLine})) {
      • print STDERR "\t$referenceType to non-existent line $referencedLine in $lineNumber\n";
    • }
  • }
  1. In fact, I haven’t seen a BASIC line longer than 251 characters in Rainbow, but one did get extended out to 255 characters in a correction. I’m guessing that an edit at some point caused the CoCo to lose those extra four characters because they went over a maximum of some kind.

  2. If a BASIC code line happens to wrap at number and then space at the 33rd character, the script assumes that this is a continuation of the previous line. But if a line happens to be 32 characters long and then the next line number is mistyped, the script will fail to detect the too-low line number and instead concatenate the two lines.

  3. If there are parentheses inside quotes and they don’t match up, the script will currently detect that as a problem. But again, I haven’t seen this in real life. Normally, even in strings there will be a close parenthesis for every open parenthesis.

  1. <- CoCo RCHECK+
  2. Make BASIC Fun Again ->