I honestly don't remember these structures when I ran BASIC-PLUS on RSTS/E, so I never used them.
I did, however, use them pretty heavily when I moved to BASIC-PLUS on the VAX. I loved BASIC-PLUS on the VAX. We used it mostly with its first class integration with RMS. My singular complaint at the time was its reliance on line numbers for ON ERROR GOTO, even though line numbers were optional. I'm sure if I tweaked my technique, I could have alleviated that a bit, but it certainly didn't feel idiomatic to me at the time, so it ended up a bit of a mixed mess.
We used to call BASIC-PLUS "BAS-CAL-TRAN" because of it being a BASIC with the structured elements of Pascal as well as features of FORTRAN (notably the COMMON blocks).
It was not uncommon for me to used structures like:
SUM = SUM + A(I) UNLESS A(I) < 0 FOR I = 1 TO 10
It was not a large leap for me to find this really readable. I think when read aloud it's more "english like" than the alternative.
I can't speak to what implementation advantage these offer. They, arguably, complicate the compiler (which means a slower and/or larger compiler, both resources which were quite limited at the time). That suggest someone simply liked the syntax better to make it worth supporting. Perhaps there was a tangible runtime boost to make it worthwhile.
You don't find these syntaxes in MS-BASIC simply because MS-BASIC (most BASICs) are running an interpreted stream of tokens. You can visualize each statement being executed directly. For example, when NEXT I
is executed in a "normal" BASIC, the interpreter honestly doesn't know when it starts if there's even a FOR statement in effect, much less that it's using I. At execution, the NEXT I
is evaluated in situ, and goes hunting.
Now, clearly, when you have something like:
SUM = SUM + I FOR I = 1 to 10
The SUM
statement can't be evaluated until the FOR statement is evaluated (in order to properly populate I
), but with a MS-BASIC like interpreter, it simply "doesn't know" that the FOR
is coming. Supporting such a syntax certainly complicates the runtime.
VAX BASIC-PLUS was compiled (i.e. there was a compile phase, whether it was compiled to machine code, I can't say). BASIC-PLUS on RSTS/E, when I used it, was not compiled. At least, I never compiled it. It ran like MS-BASIC. Type in commands, hit "RUN", rinse, repeat. Obviously, when lines are parsed, the parser could internally rewrite:
SUM = SUM + I FOR I = 1 TO 10
as:
FOR I = 1 TO 10:SUM = SUM + I: NEXT I
If it wanted to (if that's, indeed, how it was done).
But I have no memory of RUN
taking any extraordinary amount of time to start up (as if it's running through the program and pre-compiling it, or anything like that). And we tended to write some large programs in BASIC on RSTS/E (we were always running out of memory).
One feature of MS-BASIC was simply that the when you typed LIST
, it was as much a "decompiler" as anything. When a line of code is entered in MS-BASIC, it is tokenized (for example PRINT
is store as some number, vs the text P R I N T
), and this representation is what it stored in memory. This saved memory. When you typed LIST
, it would see the token for PRINT
, and then dump out the text on the screen.
Now, rewriting the BASIC code from the alternate syntax to the "normal" syntax, internally, you can see that can sort of mess up a LIST
implementation like in MS-BASIC.
So, I don't know how on RSTS/E BASIC-PLUS manifested these statements internally. But, either way, clearly BASIC-PLUS was a more sophisticated environment, and more complicated runtime. It helps not being limited to 8K of ROM. Even still, historically, this syntax style never really made it in to the MS style systems. It wasn't brought forward to VB, for example (which essentially had "unlimited" resources in terms of compiler and runtime complexity, especially compared to a PDP-11).
Addendum:
To highlight on @dirkt comment, and to expand on this answer.
I did some exploration, fired up RSTS on SimH, and poked around. Dug through some manuals to get a crash course in BASIC-PLUS I/O and TECO to write a crude hex dump program.
The key outcome, as Dirk mentioned, is that BP is compiled. Not in to machine code, but in to a "p-code", apparently known as "push/pop" code. Stack "machines" were reasonably popular in the day, with the UCSD-P system probably being the most well known.
The distinction between a compiled language, and what the MS-BASIC model implements is important. The MS-BASIC model is, essentially, a parse tree and an interpreter. In BP, the language is compiled in to an actual machine language of its own, even if it's a p-machine. That means that the p-code is un-related to actual source code.
In MS-BASIC, the parsed, tokenized form is essentially a 1:1 relationship with the what the interpreter actually executes, albeit in abbreviated form. In MS-BASIC, for example, were you to do something like: A = 1 + 2
, there's no opportunity for the language processor to convert that in to simply: A = 3
through constant folding. It doesn't have that opportunity because of the requirement that the intermediate form represents the original source code.
In a compiled language the compile does have opportunity to do these things, as the goal is semantic equivalence rather than strict interpretation. Now, I can't say whether BP performed anything like this kind of optimization, but rather it has an opportunity to if it chooses to do so.
To the point of the statement modifiers, S = S + I IF I > 0
is, while not impossible, certainly more difficult to implement in the MS-BASIC model, since in that model it interprets the stream of tokens as it sees them, with little "global" knowledge. Whereas the compiler model of BP can readily rewrite those types of statement in to an internal canonical form, that is the compiled appropriately.
Consider this program:
10 PRINT FNA(2)
20 DEF FNA(X)=X*3
If BASIC-PLUS, this is a legal program (much to my surprise), as COMPILING (which happens when the lines are entered) the DEF FNA
is, apparently, enough to register the function properly with the run time system. In contrast, in MS-BASIC this program causes an error on line 10, because the DEF FNA
has not yet been EVALUATED. One can quibble about the "right" or "wrong" of either of these.
Now, the motivations behind the use of the statement modifiers are still not clear. I'm of the opinion that they're simply syntax sugar. While they offer a potentially more compact form at a source code level, it's not really clear to me that it's enough to complicate the runtime in order to support it. Whatever memory savings in source code is balanced by runtime costs in the compiler, which is also memory bound. The BP runtime system consumed 32K of the 64K available to user processes. If they were truly interested in memory conservation, then adopting a model similar to MS-BASIC would have lead to higher gains. Keeping the source separate from the compiled binary certainly had an impact on overall memory consumption.
As to performance, at a gut level I think that the BP system would perform better than the MS-BASIC style interpreter. Simply because since its an actual compiler, it doesn't have to suffer many of the runtime constraints of an MS style interpreter, including things like scanning for line numbers and labels. At compile time, it can Just Know. Mind, that doesn't mean that it actually does. It still has to deal with the interactive nature of the system, and ad hoc editing.
If I had GOTO 10 someplace in my code, and it was magically pointed to "line 10", what happens when I delete line 10? what happens when I add a line 5? Perhaps it maintains a lookup table of line numbers. There are all sort of things it can do.
It seems to me, however, that the BP runtime is focused on runtime performance in balance with memory consumption (thus the p-code). But at the same time, it may simply have not occurred to the creators to do what the MS-BASIC implementors did in terms of an implementation model.
It would be interesting to know of any other interactive BASIC systems the were implemented similar to BP vs the MS model. It would also be interesting to learn where the inspiration behind the MS model. Did they come upon it on their own, implement something they had seen before? Maybe a new question for the forum.
Addendum 2:
To clarify for @MauryMarkowitz.
We distinguish between p-code and tokenization. P-code is "just like" machine code, it's just machine code for an ideal, pseudo machine rather than a specific CPU. It's notable as typically, especially on the older architectures, to be more compact than actual machine code.
Tokenization is basically a step past parsing. Reducing commands to internal "tokens", or ids (like the text "PRINT" to the value 123. Converting numeric source strings to binary, etc. Languages have, typically several phases of processing. The first two are commonly converting the text in to tokens (this phase is called "lexing", lexicographic analysis), then parsing.
Lexing converts raw text in to higher level constructs (i.e. tokens), then the parse phase works off of tokens instead of raw text.
For example, given a string:
IF "BOB" 123 FOR THEN
That's could lex into <IF-KEYWORD>, <STRING>, <NUMBER> <FOR-KEYWORD>, <THEN-KEYWORD>
. The lexer can identify all of these, it's up to the parser to enforce actual syntax.
MS-BASIC tends to do both when a line of code is entered. It lexes the string in to tokens, then it parses for syntax correctness. At the end, you end up with a stream of tokens, in fact you have the same string of tokens you started with. But now, at the end of the process, you "know" that the token list is syntactically correct. This can be sent to an evaluator without the need for it to do any extra error checking (beyond simple corruption).
At the end, this tokenized form is NOT "compiled code". It's an intermediate state for use by the evaluator.
P-code is "compiled code".
Consider this example: (using contrived elements)
A = 1 + 2 * B
As a token stream, this looks like: <VAR-A> <ASSIGN> <NUMBER-1> <PLUS> <NUMBER-2> <MULTIPLY> <VAR-B>
.
This is what is sent to the evaluator.
In a P-code, it will look something like this:
PUSH 2
PUSH <VAR-B>
MULTIPLY
PUSH 1
ADD
STORE A
This is a simple stack machine. Like an HP calculator.
A key thing to note is how different the equation looks. In the token stream, it's still roughly in "algebraic" form. The evaluator still needs to deal with issues of operator precedence, among other things.
In the second form, the compiler has already dealt with that and created instruction stream appropriately. The compiler can actually rewrite the source code in to a form more suitable for it's efficient execution.
Using our IF-MODIFIER, as @Dirkt mentioned:
A = A + 1 IF A > 0
The token stream is: <VAR-A> <ASSIGN> <VAR-A> <PLUS> <NUMBER-1> <IF-KEYWORD> <VAR-A> <GREATER-THAN> <NUMBER-0>
Taken at face value, an evaluator does not have enough information properly evaluate this. The compiler gets to process it as whole, and can rewrite it, into something like this:
PUSH <VAR-A>
PUSH 0
COMPARE
BRANCH-LESS-THAN-EQUAL LBL1
PUSH A
PUSH 1
ADD
STORE A
LBL1:
You'll notice, again, this looks nothing like the source code or the token stream.
This is a key distinction between a tokenized stream and a compiled representation. There's nothing stopping MS-BASIC from going the compile to p-code route, it just turns out that the tokenized representation has memory savings over the compiled version. You get a "running program" for little more than the memory cost of the source code, vs the compiled version where you have to maintain both the source code and the compiled artifact.
This is notable for an interactive environment, obviously not an issue for a normal "compiled" system. But RSTS BP is an interactive system with a compiler, which makes it stand out in the world of BASICs.
IF
statements to break the main "program flow")... nowadays, I suspect I might find it less readable as well!