2. Control Structures
In this chapter all the control structures in Pike will be explained. Control structures are used to control the flow of the program execution. Note that functions that make the program pause and simple function calls are not qualified as control structures.
2.1. Conditions
Conditions are control structures that, given a test condition selects what code to be executed. These range from the binary "execute or not" to a large table of code where the selection of which code to run is based on an input parameter.
2.1.1. if
The simplest one is called the if statement. It can be written anywhere where a statement is expected and it looks like this:
if( expression ) statement1; else statement2;
Please note that there is no semicolon after the parenthesis or after the else. Step by step, if does the following:
- First it evaluates expression.
- If the result was false go to point 5.
- Execute statement1.
- Jump to point 6.
- Execute statement2.
- Done.
This is actually more or less how the interpreter executes the if statement. In short, statement1 is executed if expression is true otherwise statement2 is executed. If you are not interested in having something executed if the expression is false you can drop the whole else part like this:
if( expression ) statement1;
If on the other hand you are not interested in evaluating something if the expression is false you should use the not operator to negate the true/false value of the expression. See FIXME: chapter for more information about the not operator. It would look like this:
if( ! expression ) statement2;
Any of the statements here and in the rest of this chapter can also be a block of statements. A block is a list of statements, separated by semicolons and enclosed by brackets. Note that you should never put a semicolon after a block of statements. The example above would look like this;
if ( ! expression ) { statement; statement; statement; }
It is also possible to place several if statements in sequence, so that if the first expression is false it continues with the next one and the next one until the first true expression is found.
if ( expression1 ) statement1; else if ( expression2 ) statement2; else if ( expression3 ) statement3; else statement4;
A special case of the above example is when in every expression you compare one variable with different values. For those applications the switch statement described below can be used instead to increas performance and simplify the code.
2.1.2. switch
A more sophisticated condition control structure is the switch statement. A switch lets you select one of many choices depending on the value of an expression and it can look something like this:
switch ( expression ) { case constant1: statement1; break; case constant2: statement2; break; case constant3 .. constant4: statement3; break; case constant5: case constant6: statement4; // Fallthrough default: statement5; }
As you can see, a switch statement is a bit more complicated than an if statement. It is still fairly simple however. It starts by evaluating the expression it then searches all the case statements in the following block. If one is found to be equal to the value returned by the expression, Pike will continue executing the code directly following that case statement. When a break is encountered Pike will skip the rest of the code in the switch block and continue executing after the block. Note that it is not strictly necessary to have a break before the next case statement. If there is no break before the next case statement Pike will simply continue executing and execute the code after that case statement as well.
One of the case statements in the above example differs in that it is a range. In this case, any value between constant3 and constant4 will cause Pike to jump to statement3. Note that the ranges are inclusive, so the values constant3 and constant4 are also valid.
2.2. Loops
Loops are used to execute a piece of code more than once. Since this can be done in quite a few different ways there are four different loop control structures. They may all seem very similar, but using the right one at the right time makes the code a lot shorter and simpler.
2.2.1. while
While is the simplest of the loop control structures. It looks just like an if statement without the else part:
while ( expression ) statement;
The difference in how it works isn't that big either, the statement is executed if the expression is true. Then the expression is evaluated again, and if it is true the statement is executed again. Then it evaluates the expression again and so forth... Here is an example of how it could be used:
int e=1; while(e<5) { show_record(e); e=e+1; }
This would call show_record with the values 1, 2, 3 and 4.
2.2.2. for
For is simply an extension of while. It provides an even shorter and more compact way of writing loops. The syntax looks like this:
for ( initializer_statement ; expression ; incrementor_expression ) statement ;
For does the following steps:
- Executes the the initializer_statement. The initializer statement is executed only once and is most commonly used to initialize the loop variable.
- Evaluates expression
- If the result was false it exits the loop and continues with the program after the loop.
- Executes statement.
- Executes the increment_expression.
- Starts over from 2.
This means that the example in the while section can be written like this:
for(int e=1; e<5; e=e+1) show_record(e);
2.2.3. do-while
Sometimes it is unpractical that the expression is always evaluated before the first time the loop is executed. Quite often you want to execute something, and then do it over and over until some condition is satisfied. This is exactly when you should use the do-while statement.
do statement; while ( expression );
As usual, the statement can also be a block of statements, and then you do not need a semicolon after it. To clarify, this statement executes statement first, and then evaluates the expression. If the expression is true it executes the loop again. For instance, if you want to make a program that lets your modem dial your Internet provider, it could look something like this:
do { modem->write("ATDT441-9109\n"); // Dial 441-9109 } while(modem->gets()[..6]] != "CONNECT");
This example assumes you have written something that can communicate with the modem by using the functions write and gets.
2.2.4. foreach
Foreach is unique in that it does not have an explicit test expression evaluated for each iteration in the loop. Instead, foreach executes the statement once for each element in a set. Foreach has two syntaxes, the first of which look like this:
foreach ( array_expression, variable ) statement;
We have already seen an example of foreach in the find_song function in chapter 2. What foreach does is:
- It evaluates the array_expression which must return an array.
- If the array is empty, exit the loop.
- It then assigns the first element from the array to the variable.
- Then it executes the statement.
- If there are more elements in the array, the next one is assigned to the variable, otherwise exit the loop.
- Go to point 4.
Foreach is not really necessary, but it is faster and clearer than doing the same thing with a for loop, as shown here:
array tmp1 = array_expression; for ( tmp2 = 0; tmp2 < sizeof(tmp1); tmp2++ ) { variable = tmp1 [ tmp2 ]; statement; }
The second syntax for foreach is the more flexible iterator syntax, which can be used to iterate over arrays, mappings, multisets and strings as well as some objects. It also has the option of providing the index value:
foreach ( iterable_expression; optional_index_variable; optional_value_variable ) statement;
This is approximately equivalent to:
for (Iterator iter = get_iterator(iterable_expression); iter; iter->next()) { optional_index_variable = iter->index(); optional_value_variable = iter->value(); statement; }
See get_iterator()
for further details.
2.3. Breaking out of loops
The loop control structures above are enough to solve any problem, but they are not enough to provide an easy solution to all problems. One thing that is still missing is the ability to exit a loop in the middle of it. There are three ways to do this:
2.3.1. break
break exits a loop, switch, or catch statement immediately and continues executing after the loop. Break can not be used outside of one of these. It is quite useful in conjunction with while(1) to construct command parsing loops for instance:
while(1) { string command=Stdio.Readline()->read("> "); if(command=="quit") break; do_command(command); }
When you want to break out of more than the innermost loop, you can tell break what loop to break free of using lables, as in:
array arr1, arr2; while(1) { // ... a:if(sizeof(arr1) >= sizeof(arr2)) { int i = sizeof(b), j = sizeof(yb); while(i) if(b[--i] != yb[--j]) break a; // the code here is only run when arr1 // and arr2 share some common suffix } // execution continues here }
Inside a catch block, break will terminate the block with no exception being thrown; catch will return zero, as if execution had continued to the end of the block.
2.3.2. continue
Continue does almost the same thing as break, except instead of breaking out of the loop it only breaks out of the loop body. It then continues to execute the next iteration in the loop. For a while loop, this means it jumps up to the top again. For a for loop, it jumps to the incrementor expression. For a do-while loop it jumps down to the expression at the end. To continue our example above, continue can be used like this:
while(1) { string command=Stdio.Readline()->read("> "); if(strlen(command) == 0) continue; if(command=="quit") break; do_command(command); }
This way, do_command will never be called with an empty string as argument.
2.3.3. return
Return doesn't just exit the loop, it exits the whole function. We have seen several examples how to use it chapter 2. None of the functions in chapter two returned anything in particular however. To do that you just put the return value right after return. Of course the type of the return value must match the type in the function declaration. If your function declaration is int main() the value after return must be an int. For instance, if we wanted to make a program that always returns an error code to the system, just like the UNIX command false this is how it would be done:
#!/usr/local/bin/pike int main() { return 1; }
This would return the error code 1 to the system when the program is run.
2.3.4. continue return
Continue return exits a restartable (__generator__
or __async__) function temporarily. The next time the restartable
function is called, it will resume from the place where the continue
return was. An alternative to using continue return is the
function predef::yield()
, which allows resumption with a value.
Note that if the restartable function has been called concurrently
(ie before the continue return statement has exited the function),
the next continue return (or predef::yield()
) will not
exit the function, but instead just resume, and the value to be returned is
thrown away. The concurrent call will have received the return value
UNDEFINED. Only information about the latest concurrent call is
retained.
Continue return is only available in Pike 9.0 and later.