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:
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 |
---|---|---|---|---|---|---|
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.
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 RUN OS/8 RUNNH READY
10 PRINT 2+2\END
RUN
HELLO BA 5B
4
READYREADY
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 RUN TOPS-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
READY10 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.
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.
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 statements Single statements 10 A=1:PRINT A
20 B=210 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.)
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 END One END 10 GOSUB 30
20 END
30 PRINT 1\RETURN10 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 BASIC10 PRINT 1
20 END
30 PRINT 2
This doesn’t work in TOPS-20 BASIC, throwing the error: ? Statement outside of program.
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 END10 A=1
20 IF A=1 THEN 40
30 GOTO 50
40 PRINT A
50 END10 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…)
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.
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 printingUM (MTS) BASIC
double-spacedUM (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.
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).
[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.
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:
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 .
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 .
% 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 .^E Simulation stopped, PC: 01210 (JMP 1207) sim> exit Goodbye RX: writing buffer to file %
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.