Comparing BASIC capabilities of different systems

While I had always realized, from reading the excellent Usborne coding books, that there were differences between BASIC on different systems, I never gave it much thought; at the time, I only had access to Apple ][ computers.

Not long ago, I wrote some simple BASIC programs for the Altair 8800, and attempted to port them to OS/8 BASIC on an emulated PDP-8/e using SIMH. (At some later point, I might make a separate notes page for that.) I was surprised to see how many differences there were between these different BASIC versions.

This page is meant to be a reference for these differences, to aid converting from one BASIC to another. Hopefully I can expand it to more systems as time permits.

Table of contents:

Table of capabilities of various systems

The following table shows both successes and failures, depending on whether the feature is offered in that particular BASIC.

Test Altair BASIC 1.0 Applesoft BASIC Integer BASIC
( original Apple ][ )
OS/8 BASIC TOPS-20 BASIC U of M (MTS) BASIC

Birds of a feather

You can see many similarities between products made by the same company. For instance, both DEC BASIC versions use \ for multi-line statements; and both won’t accept any additional code paths beyond the END statement.

Similarly for both Apple products, although they were developed by different people at different times, they share a common philosophy about PRINT spacing: neither Applesoft nor Integer BASIC adds any additional space around numbers. There are pros and cons to this, but certainly it gives the coder greater flexibility, since it’s easier to add one’s own space when desired than to try to ‘remove’ it later somehow.

TOPS-20 BASIC can afford to be much more verbose compared to OS/8 BASIC, since TOPS-20 was assumed to be running on a fairly beefy computer (for its day), with a lot greater storage capacity. This is also true for UM BASIC.

Why RUNNH?

The NH part of RUNNH stands for no header, which suppressed a header, a common feature on ‘larger’ systems of that era – larger than a microcomputer, that is.

Here’s a comparison of OS/8 BASIC with and without the header:

OS/8 RUNOS/8 RUNNH
READY
10 PRINT 2+2\END
RUN


HELLO   BA    5B

 4

READY

READY
10 PRINT 2+2\END
RUNNH

 4

READY

Not a huge difference, but all of those extra lines would’ve added up in the examples above. (I have no idea what that 5B is doing there, but it’s there every time I run a program.)

Now for TOPS-20:

TOPS-20 RUNTOPS-20 RUNNH
10 PRINT 2+2
RUN


NONAME.B20
Saturday, October 26, 2019 00:51:02

 4

Run time: 0.007 secs            Elapsed time: 0:00:00

READY

10 PRINT 2+2
RUNNH

 4

READY

Probably these headers were appreciated by the users back when programs may have taken some time to run, and perhaps submitted as batch jobs rather than executed interactively.

Solving compatibility issues

Some ‘deficiencies’ in a particular BASIC version are trivial to work around. For example, if it requires an END statement – add one! But not all problems are as easily dispatched.

Multi-statement lines

Most systems seem to use colon (:) as the statement separator, with OS/8 and TOPS-20 using backslash (\). So converting from one to the other is just a single character replacement.

The only system I have found thus far to not support multiple statements per line is UM (MTS) BASIC. Its documentation (p. 325) says:

“Standard” BASIC is defined as the original BASIC System implemented at Dartmouth College. […]

Other versions of BASIC have a few common facilities that U of M BASIC does not have, namely multiple statements per line and a multiple assignment statement. However, U of M BASIC has all the important statements with many extensions.

That last sentence seems a bit self-conscious. :-)

In any event, the fix here is straightforward, but maybe a bit tedious to implement. You have to break these multi-statement lines up, eg:

Multiple statementsSingle statements
10 A=1:PRINT A
20 B=2
10 A=1
15 PRINT A
20 B=2

To make this a bit easier, I wrote a small Python script that converts BASIC numbers into labels. Then you can break the statements up with a bit of regex and run the inverse program which converts the labels back to new numbers. (More on this later.)

Code after END

This one caught me by surprise: the DEC BASIC systems won’t permit any additional lines of BASIC after the END statement. The fix here is not difficult: (1) add a line 99999 END to your program, and (2) replace all other END statements with GOTO 99999.

Mid-program ENDOne END
10 GOSUB 30
20 END
30 PRINT 1\RETURN
10 GOSUB 30
20 GOTO 99999
30 PRINT 1\RETURN
99999 END

In OS/8 BASIC, it’s fine to have code, comments, etc after the END statement, but only if they don’t execute. Here’s a silly, contrived example:

Perfectly legal in
OS/8 BASIC
10 PRINT 1
20 END
30 PRINT 2

This doesn’t work in TOPS-20 BASIC, throwing the error: ? Statement outside of program.

IF condition

Jumping to a line. There are three ways to do this:

The only one supported by every BASIC I tested so far is the first one. Thus if you just want to jump to a line, choose that form.

Executing a statement. Most support this, with OS/8 BASIC being the notable abstainer. Working around this is not terribly easy, because you either have to jump twice (if you want to keep your condition the same), or invert your condition logic. Hopefully this small example illustrates this:

IF with statements IF with jumps
(same logic)
IF with jumps
(inverted logic)
10 A=1
20 IF A=1 THEN PRINT A
30 END
10 A=1
20 IF A=1 THEN 40
30 GOTO 50
40 PRINT A
50 END
10 A=1
20 IF A<>1 THEN 40
30 PRINT A
40 END

It’s possible to programmatically change these – even inverting the logic along the way – although the final program output will be a bit bigger, and maybe less recognizable.

(This vaguely remninds me of FORTRAN’s now-obsolete “arithmetic IF” statements…)

Dynamic DIM

Not much to say here – if your BASIC doesn’t support using a variable in DIM, you need to pass a constant. Just make it big enough to hold whatever maximum value your variable might’ve been. I find it kind of funny that the ‘big’ systems don’t support this, and the microcomputers do.

PRINT spacing

What a mess! I/O shows the most diversity amongst all of the BASICs tried, with little agreement between them. To generalize, most add extra space around integers, except the Apple variants. Thus if you need to print two numbers adjacent, you’ll need to convert to string first. Here’s an example of this in action:

Altair BASIC 1.0 Applesoft BASIC Integer BASIC
( original Apple ][ )
OS/8 BASIC TOPS-20 BASIC U of M (MTS) BASIC

Altair BASIC. Due to memory constraints, BASIC 1.0, 4K BASIC 3.2, and 4K BASIC 4.0 all lack string functions. That is why I wrote an 11-line subroutine to print single digits as strings.

Although the situation improves in 8K BASIC, STR$(X) still adds spaces! So you have to handle those, too. Here it is compared with TOPS-20 BASIC:

TOPS-20 BASIC Altair 8K BASIC 3.2
(default STR$ spacing)
Altair 8K BASIC 3.2
(corrected spacing)

UM (MTS ) BASIC. The most obvious change is that the STR$ function is called NTS (number-to-string) in UM BASIC. But that’s not the most interesting difference.

You may have noticed in this example and the previous one that I added a space character before the string, yet none printed. That is called a carriage control character, which determines how to format the output. This processing happens only when the first item passed to PRINT is a string. The MTS BASIC documentation (pp. 177, 184, and 322) give a full description of this. Here’s an example:

UM (MTS) BASIC
default printing
UM (MTS) BASIC
double-spaced
UM (MTS) BASIC
triple-spaced

Notice that in the string "Hello", I didn’t put a space; that is because the first character, "H" is not a valid carriage control character. Contrast this with the previous examples, where the string "0" or "4", respectively, were the first to be printed. This would’ve changed the output in an unintended way.

PRINT without CR/LF

This behaviour of always printing on a new line for every PRINT statement – even if the list terminates with a semicolon (;) – seems particular to UM BASIC. I was hopeful I could use the & character to suppress carriage return, however, it also changes the way the text is displayed on the terminal.

Here’s what that looks like on a 3270 terminal:

 : 10 print "Hello"
 : 20 print "World"
 : run
   Hello
   World
 +  Program Ends
 : 10 print "&Hello"
 : run
  Hello World
 +  Program Ends
 : 

Unfortunately a space is always added, even if there aren’t any in the input. (Plus, the output colour changes to the prompt colour which, although suboptimal, isn’t a deal-breaker on its own.) So that leaves string concatenation:

 : 10 print "Hello"+"World"
 : 20
 : run
 &  Illegal operator or missing operand.
 &  Illegal input/output list.
   10 print "Hello"+"World"
 &  Error(s) in program. - Correct and try again.
 : 10 a$="Hello"
 : 20 b$="World"
 : 30 print a$+b$
 : run
 &  Illegal input/output list.
   30 print a$+b$
 &  Error(s) in program. - Correct and try again.
 : 30 c$=a$+b$
 : 40 print c$
 : run
   HelloWorld
 +  Program Ends
 : 

It’s a bit tedious, because you must explicitly create a new string composed of only two other strings – you can’t concatenate several strings together (without looping).

Using OS/8 BASIC

[This section likely to be moved to a separate page at some point.]

While trying to figure out how to get OS/8 BASIC to coöperate, I stumbled across a blog post from “Big Dan the Blogging Man”, where he writes:

I wanted to do a quick “Hello World” test in a programming language on the PDP-8 but it was not to be. Something is wrong with the standard BASIC interpreter. It fails to function on any of the PDP-8 OS/8 distributions I have tried. I’ve also tried multiple versions of SIMH. No luck.

I had problems, too, so I thought I might give a bit of an overview of how I got going.

Where to get OS/8 BASIC

After a lot of failed effort in Google land, I remembered that SIMH has a bunch of “Software Kits”, including images for OS/8 and TSS/8 (not tried yet). Of course you’ll need to obtain a copy of SIMH, too.

There are three documentation sources I found most helpful:

False starts

Booting and loading BASIC is really trivial. The system has booted when you see the dot prompt (.).

But it doesn’t like my first few attempts to talk to it:

% pdp8

PDP-8 simulator V3.10-0
sim> attach rx0 Disks/os8_rx.dsk
RX: buffering file in memory
sim> b rx

.R BASIC
NEW OR OLD--NEW
FILE NAME--HELLO

READY
PRINT "HELLO, WORLD!"
WHAT?
	

WHAT? What do you mean, what? :-)

Okay, try something simpler then:

PRINT 2+2
WHAT?
	

Argh! I guess this BASIC doesn’t have an ‘immediate’ mode. Fine, try a program version:

10 PRINT "HELLO, WORLD!"
RUN

HELLO   BA    5B

ME 10

READY
	

The OS/8 BASIC Help page says that ME means “missing END statement”. Okay, add one:

20 END
LIST

HELLO   BA    5B

10 PRINT "HELLO, WORLD!"
20 END

READY
RUN

HELLO   BA    5B

BAD FILE
	

BAD FILE? Oh, maybe I should SAVE first:

SAVE
BAD FILE
	

After much head-scratching and doc reading, I came up with the answer: no free disk space. Say BYE to BASIC first, then check DIR:

BYE

.DIR

         

SYS  VOLUME--   1
SYS:=RX8E
OS/8 SYSTEM   VERSION   3Q

BUILD .SV  33           HELP  .SV   8           BASIC .UF   4
ABSLDR.SV   5           PAL8  .SV  19           BCOMP .SV  17
BITMAP.SV   5           PIP   .SV  11           BLOAD .SV   8
BOOT  .SV   5           PT8E  .BN   1           BRTS  .SV  15
CCL   .SV  18           RESORC.SV  10           EABRTS.BN  24
CREF  .SV  13           RXCOPY.SV   6           RESEQ .BA   6
DIRECT.SV   7           SABR  .SV  24           ECHO  .SV   2
EDIT  .SV  10           TECO  .SV  22           RKLFMT.SV   9
EPIC  .SV  14           BASIC .AF   4           SET   .SV  14
FBOOT .SV   2           BASIC .FF   4           BATCH .SV  10
FOTP  .SV   8           BASIC .SF   4           FUTIL .SV  26
HELP  .HL  55           BASIC .SV   9           IDS   .SV   5

  36 FILES IN  437 BLOCKS -    1 FREE BLOCKS

.

Freeing up disk space for the “workspace file”

The OS/8 Language Reference Manual, p. 1-49 (PDF p. 60) says:

The RUN and RUNNH commands also cause BASIC to store a copy of the program it is running in a file called BASIC.WS.

This floppy disk, RXA0 to OS/8, or RX0 (os8_rx.dsk) to SIMH, is somewhere around 256 KB, probably a bit less when formatted – and it’s totally full. Amazing what they fit on a floppy disk in those days…

You have a couple of options: (1) free up some disk space; (2) copy everything to a bigger floppy or hard disk; (3) figure out how to convince BASIC to write somewhere other than SYS:BASIC.WS. I chose (1), since I can access the help system in my web browser:

.DEL HELP.*
FILES DELETED:
HELP.HL
HELP.SV

.

Now there’s enough disk space and BASIC plays nice:

.R BASIC
NEW OR OLD--NEW
FILE NAME--HELLO

READY
10 PRINT "HELLO, WORLD!"
20 END
RUN

HELLO   BA    5B    

HELLO, WORLD!

READY
BYE

.DIR BASIC.WS,HELLO.BA

         

SYS  VOLUME--   1
SYS:=RX8E
OS/8 SYSTEM   VERSION   3Q

BASIC .WS   1           

   1 FILES IN    1 BLOCKS -   63 FREE BLOCKS

.

Notice that, unless you SAVE, only the workspace file remains. Of course, as the manual points out, if you forget to SAVE you can retrieve your last-executed source from BASIC.WS and SAVE it at that point:

.R BASIC
NEW OR OLD--OLD
FILE NAME--BASIC.WS

READY
SAVE HELLO

READY
BYE

.DIR HELLO.BA

         

SYS  VOLUME--   1
SYS:=RX8E
OS/8 SYSTEM   VERSION   3Q

HELLO .BA   1           

   1 FILES IN    1 BLOCKS -   62 FREE BLOCKS

.

A complete “Hello, World” session

% pdp8

PDP-8 simulator V3.10-0
sim> attach rx0 Disks/os8_rx.dsk
RX: buffering file in memory
sim> b rx0

.DEL HELP.*
FILES DELETED:
HELP.HL
HELP.SV

.R BASIC
NEW OR OLD--NEW
FILE NAME--HELLO

READY
10 PRINT "HELLO, WORLD!"
20 END
RUN

HELLO   BA    5B    

HELLO, WORLD!

READY
BYE

.
Simulation stopped, PC: 01210 (JMP 1207)
sim> exit
Goodbye
RX: writing buffer to file
% 

Factorials

Let’s try something a little more interesting than “Hello, World”. The idea for this program didn’t originate with me – when I figure out where I came across this, I’ll update this page. I will claim responsibility for the right-alignment of numbers, however.

READY
10 H=7
20 L=0
30 DIM I(15)
40 FOR J=0 TO H
50 I(J)=0
60 NEXT J
70 N=2
80 I(H)=2
90 N=N+1
100 FOR J=H TO 0 STEP -1
110 I(J)=I(J)*N
120 NEXT J
130 FOR J=H TO 0 STEP -1
140 IF I(J)<1000 THEN 180
150 I(J-1)=I(J-1)+1
160 I(J)=I(J)-1000
170 GOTO 140
180 NEXT J
190 IF N>=10 THEN 210
200 PRINT " ";
210 PRINT N;"! =";
220 X=0
230 FOR J=0 TO H
240 IF I(J)=0 THEN 260
250 X=X+1
260 IF X=0 THEN 440
270 IF X>1 THEN 370
280 IF L=1 THEN 340
290 PRINT TAB((J+1)*4+4);
300 IF I(J)>=10 THEN 320
310 PRINT " ";
320 IF I(J)>=100 THEN 340
330 PRINT " ";
340 PRINT I(J);
350 X=2
360 GOTO 440
370 T=INT(I(J)/100)
380 U=INT((I(J)-T*100)/10)
390 V=INT(I(J)-T*100-U*10)
400 Z=T \ PRINT STR$(Z);
410 Z=U \ PRINT STR$(Z);
420 Z=V \ PRINT STR$(Z);
430 PRINT " ";
440 NEXT J
450 PRINT
460 IF I(0)=0 THEN 90
470 END
RUN

FACT    BA    5B    

  3 ! =                               6 
  4 ! =                              24 
  5 ! =                             120 
  6 ! =                             720 
  7 ! =                           5 040 
  8 ! =                          40 320 
  9 ! =                         362 880 
 10 ! =                       3 628 800 
 11 ! =                      39 916 800 
 12 ! =                     479 001 600 
 13 ! =                   6 227 020 800 
 14 ! =                  87 178 291 200 
 15 ! =               1 307 674 368 000 
 16 ! =              20 922 789 888 000 
 17 ! =             355 687 428 096 000 
 18 ! =           6 402 373 705 728 000 
 19 ! =         121 645 100 408 832 000 
 20 ! =       2 432 902 008 176 640 000 
 21 ! =      51 090 942 171 709 440 000 
 22 ! =   1 124 000 727 777 607 680 000 

READY
	

You can change the value of H in line 10 to have greater or fewer factorials printed; the number corresponds to the number of ‘buckets’ (each ‘bucket’ being three decimal places). Currently there’s a limit of 15 buckets (line 30), but you could change that, too.

If you don’t like the right-justification, you can set L=1 in line 20, and you’ll get:

READY
20 L=1
RUNNH
  3 ! = 6 
  4 ! = 24 
  5 ! = 120 
  6 ! = 720 
  7 ! = 5 040 
  8 ! = 40 320 
  9 ! = 362 880 
 10 ! = 3 628 800 
 11 ! = 39 916 800 
 12 ! = 479 001 600 
 13 ! = 6 227 020 800 
 14 ! = 87 178 291 200 
 15 ! = 1 307 674 368 000 
 16 ! = 20 922 789 888 000 
 17 ! = 355 687 428 096 000 
 18 ! = 6 402 373 705 728 000 
 19 ! = 121 645 100 408 832 000 
 20 ! = 2 432 902 008 176 640 000 
 21 ! = 51 090 942 171 709 440 000 
 22 ! = 1 124 000 727 777 607 680 000 

READY
	

Feel free to contact me with any questions, comments, or feedback.