PRINT "0" PRINT "1" PRINT "2" PRINT "3" PRINT "4" PRINT "5" PRINT "6" PRINT "7" PRINT "8" PRINT "9"
FOR num% = 0 TO 9 PRINT num% NEXT
That is one aspect of program flow. The second is in making decisions. For example, what if we examine the identity of the currently tuned channel and it is not the channel ID we expect? Do we plough on regardless, or do we get the user to do something? To be able to have this sort of control requires the ability to make decisions; and this is what we shall look at first:
IF ( (A = 1) AND (B = 2) AND (C = 3) ) THEN ...do something...
You can do this in script by knowing that the if() syntax is:
if( <var> <condition> <number> ) <command>
If the condition evaluates to be true, then the 'command' is executed. There is no reason why the 'command' cannot be another if() command.
So, first, the conditions:
! Not equal
= Equal
< Less than
> Greater than
[ Less than or equal to
] Greater than or equal to
All of the following examples evaluate to TRUE, so the command would be executed.
set A to 123 if(A = 123) ... if(A ! 0) ... if(A > 12) ... if(A < 1973) ... if(A [ 123) ... if(A ] 123) ...
All of the following evaluate to FALSE. Remember, A is zero at script start.
if(A = 123) ... if(A ! 0) ... if(A > 12) ...
You get the idea.
Note that you can compare a float with an integer. 99.4 is less than 100, 103.2 is more...
So, that line of BASIC at the start could be written in our script as:
if(A = 1) if(B = 3) if(C = 3) ...do something...
Now let's assume you have read some data from a teletext frame, and you'd like to convert it. We shall invent an algorythm, where:
result = ( (((input << 3 ) / 4) >> 1) * 5 )
We only want to do this to numbers over 100, but we may be called with numbers less than 100. What we need is an 'if()' spanning several lines.
There are two ways to do this:
The wrong way...
if(Q ] 100) set R to Q if(Q ] 100) lsl(R, 3) if(Q ] 100) div(R, R, 4) if(Q ] 100) lsr(R, 2) if(Q ] 100) mul(R, R, 5)
The right way...
if(Q < 100) go("skipthisstuff") set R to Q lsl(R, 3) div(R, R, 4) lsr(R, 2) mul(R, R, 5) .skipthisstuff
This leads us on to...
Branches are defined as a period followed by the 'name' of the location. This is called a label, and looks like:
or:.branchpoint
Labels can contain practically any printable character (except space) as they are treated as strings, however it is best to stick to upper and lower case letters and an underscore. By habit I write my labels as lower-case words run together, however you can use the style you prefer, such as:.mungecode
or:.BranchPoint
.munge_code
Under RISC OS, you can define up to sixteen labels. Under Windows, there is no limit.
All defined labels are read when the script is initialised, and subsequently you can use the go() command to jump to a label, to branch.
You can branch forwards or backwards, as all of the labels are already 'known' as the script begins execution.
message("Please set satellite receiver to \"TV5 FBS\" (channel FAV:46)") .isittv5 channelid(A) if (A ! &AF00) message("Please set satellite receiver to \"TV5 FBS\" (channel FAV:46)") if (A ! &AF00) go("isittv5")
FOR x% = 101 TO 109 PROCget_page(x%) NEXT
In our teletext script, we can achieve the same thing using recursive code and a loop. The only other complication is that the variable initialise and increment is not performed automatically.
set A to 101 .comebackhere getframes(A) A++ if (A < 110) go("comebackhere")
PRINT "Please select TV5 FBS" DO ch% = FNget_channel_id WHILE (ch% <> &AF00)
We've already seen this before:
message("Please select TV5 FBS") .isittv5 channelid(A) if (A ! &AF00) message("Please select TV5 FBS") if (A ! &AF00) go("isittv5")
while the REPEAT...UNTIL will loop until the clause is true, like:DO : Play Cricket : WHILE (It is still sunny)
REPEAT : Search for that film you haven't watched : UNTIL (You have found the DVD)
Because of this, there is no point providing an example. You are going to simply have to work out the mechanics of what you are trying to do, and then implement it using an if test and a branch. By doing this, you can implement all sorts of everything, from simple FOR...NEXT loops to SELECT CASE statements. Because of the ability to nest if statements inside other ones, you can arrange some quite complex control structures.
By way of example, I have a script that reads the temperatures of today and tomorrow's weather and writes them to file as celsius and fahrenheit. This was performed as a demonstration of the maths system and procedural code because, to be honest, I don't know fahrenheit at all. Anyway, there were two possible ways to do this:
In order to perform a procedural call, you should use the command call() (instead of go()). What this does is jump to the specified label, after saving the return location on the call stack.
When you have finished with the subroutine, you can return to the line following the call() by using the return() command.
[...setup stuff...] ; read today's high and low selectframe(201) info("Generating report...") readvalue(D, 14, 14) ; D = today's high readvalue(E, 18, 14) ; E = today's low filewritestring("Nantes today : High = ") filewritevar(D, 2) filewritestring("°C (") set A to D call("fahrenheit") filewritevar(B, 2) filewritestring("F)") filewritebyte(13) filewritebyte(10) filewritestring(" Low = ") filewritevar(E, 2) filewritestring("°C (") set A to E call("fahrenheit") filewritevar(B, 2) filewritestring("F)") filewritebyte(13) filewritebyte(10) filewritebyte(13) filewritebyte(10) [...similar code for tomorrow's forecast...] [...tidyup code...] quit() .fahrenheit ; takes a value in A, converts it to fahrenheit, ; places a rounded version of the result into B... ; CORRUPTS 'Z' and 'B' set Z to A add(Z, 40) ; Celsuis + 40 mul(Z, Z, 9) ; * 9 div(Z, Z, 5) ; \ 5 sub(Z, 40) ; - 40 [isn't there a +/- 32 algorithm?] roundcast(B, Z) return()
There is no way to arbitrarily discard or otherwise skip entries in the call stack. Every 'call()' must have a matching 'return()'. There can be more than one return in code - i.e. in the middle of a function if it detects invalid input as well as the usual one at the end of the function - so long as programmatically only one is executed per call, depending on the conditions of trigger. For example:
What this does is to simply divide Q by R and place the result into S. However, if it detects that R is zero, it aborts early to save a division-by-zero error. So there are two possible 'return()'s, but given the circumstances, only one is ever valid. Either R is zero (hence the first one) or it isn't (hence the second one).set Q to 23 set R to 0 call("divide") quit() .divide set S to 0 if (R = 0) return(); div(S, Q, R) return();
It is considered a failure to end a script with a call stack; this implies a mismatched call/return pair.
Under RISC OS, the call stack may be up to 16 entries. There is only one way to end a script under RISC OS (the terminate() command), so abandoning a script prematurely may result in warnings if a call stack is active.
Under Windows, there is no limit to the size of the call stack. Furthermore, the terminate() command is specifically intended for abandoning scripts, so you will not receive a warning if there is a call stack defined. The correct way to end a script (under Windows) is with the quit() command, and this will warn if there is a call stack.
The the lvar() command (under Windows) lists the call stack, the result looking like:
if there is a call stack active, or:Call stack: 4 --> +398 line 30 3 --> +290 line 23 2 --> +194 line 17 1 --> +101 line 6
otherwise.Call stack: -- no call stack --