TL;DR:
FOR T=<target value> TO T
uses T as a temporary variable to store the target value inside the FOR stack frame, where it is used later to compare with the actual value of T after an iteration. This eliminates the need for a second, helper variable. Works whenever T is/can be set within the loop.
The Long Read:
I imitated the code in CBM prg Studio as follows:
In my eyes this seems an even more confusing notation than the original code:
100 PRINT "START "TI$ : T=2.5: GOSUB 900 : PRINT "END TIME: "TI$
199 END
900 T0=TI: FOR T=T0+T*60 TO T : T=TI : T=T-5184E3*(T<T0) : NEXT : RETURN
But then again, I'm maybe simply used to the way a PET worked :)
But yeah, needs some knowledge about inner workings of (Microsoft) BASIC.
Line 40 is the one that confuses me. When you enter the loop, t0+t*60 evaluates to a value that, in this program, is guaranteed to be greater than t.
No, it isn't, it's the same as T - or more exact the value of T when the loop is entered first. Note that:
- T is set as part of the FOR instruction to the evaluated expression after the equal sign.
- TO evaluates and stores the target value on the stack.
- A for-loop is iterated as long the actual value is lower or equal to the target value
- When comparing, it compares the actual value of T to the stored target value.
Step two is needed less for performance enhancement but to allow FOR loops with constant values like FOR I = 1 TO 10
. After all, there is no variable to store that but the stack frame itself.
But then you get to the next 'TO t'. Why do we stay in the loop until t0+2*60?
Because the value to be compared against is not T, but T stored at the time the loop is entered, here T0+T*60.
I would assume that 'TO t' either means 'TO 2' (t's initial value) or 'TO <whatever t's value is>'.
No, it's 'TO T' with the value of T at the time the TO gets executed, so after the initial assignment of T0+T*60.
In either case, I'd think that the loop would end immediately.
Only if the actual value of T is above the limit given, as that's what FOR does. In this case, T is equal to the limit when entering the loop, so it gets executed once. This is the base to allow a FOR I=1 TO 1
loop to execute.
Execution in Detail
Let's see it step by step (leaving out framing):
100 T=2.5
- Set wait time to 2.5 seconds
GOSUB 900
- Go and wait
900 T0=TI
- Lookup actual time
FOR T=T0+T*60
- Set T to the desired end time
TO T
- Store the value of T as terminal value
T=TI
- Set T to actual time (which should be lower than taget time
T=T-5184E3*(T<T0)
- Overflow correction
NEXT
- Continue to iterate the loop if the (corrected) value of T is still lower than the saved target value.
What's the Trick used?
Instead of having a second variable for the target value, which is never used within the loop it uses the internal storage within the FOR stack frame. This is quite handy in BASIC dialects without global variables, as the subroutine does not need/define any additional variable of its own.
Couldn't TI be Used Instead?
No, TI is not a real variable but a pseudo variable mirroring the time of day clock. So while TI can be used as target value in a FOR clause, like any other variable, the value stored would be the one at time of entrance to the loop. Also, this would make any overflow handling somewhat hard.
But It Would Work With a GOTO Loop, Right?
Yes. Something around a core like this
900 T=TI+T0*60
910 IF TI < T THEN GOTO 910
920 RETURN
May work quite well (though need some fine tuning for overflow) - as usual, many ways lead to Rome.
So All of This is Commodore Specific?
No, it should work with any BASIC, not just MS supplied ones, it also complies to the ECMA-55 spec.
BUT there seem to be at least one BASIC (Locomotive, but that needs more testing) that works different.
All it needs is to adapt machine specific values. For example BBC BASIC offers a TIME pseudo variable with similar workings - except it's incremented 100 times a second (*1):
100 PRINT "START ", TIME : T=2.5: GOSUB 900 : PRINT "END TIME: ",TIME
199 END
900 T0=TIME: FOR T=T0+T*100 TO T : T=TIME : NEXT : RETURN
The BBC is an interesting case here, as its clock is not incremented monotone by 1 but by 5 or 6 (*2). The implied greater than comparison of a FOR/NEXT loop does handle this right away.
The very same code should as well work with the CPC's Locomotive BASIC, this time with a Factor of 300 (*2), but it doesn't (*3):
900 T0=TIME: FOR T=T0+T*300 TO T : T=TIME : NEXT : RETURN
(This is still under research)
Locomotive BASIC seems to assign the new value to T (as part of FOR), but then compares to the previous value of T - as if that had been stored onto it's frame before the assignment gets evaluated. Quite a strange behaviour.
How the Stack Frame for FOR is Structured
This is Commodore specific, but other basics use similar structures.
- Type Byte $81 to indicate a FOR entry
- Loop variable address (two bytes)
- Increment float (five bytes)
- Increment sign, 01 or FF (one byte)
- Target value, float (five bytes)
- Line number (two bytes)
- Return address (two bytes)
*1 - Time is only updated 18 times a second.
*2 - 300 can easy be divided by 5 or 6 do produce 50 or 60 Hz to fit into frame timing for European or US/Japanese TV
*3 - As Scruss did find by testing the program with a real CPC 6128.