Table of Contents
Control structures in a programming language allow to execute blocks of code depending on certain conditions, typically expressed as the Boolean result of an expression. Control structures also allow repeated execution of blocks of code without the need for repeating the actual code. This chapter describes the implementation and use of such structures in Biferno.
The if instruction has the following syntax:
if (expression)
{
execute block 1
}
else
{
execute block 2
}
The if instruction executes the code block between curly brackets if the
expression between parentheses returns true. If the expression returns
false, code execution will resume from the instruction after the closing
curly bracket, or from the next code line if the block consists of one line
(brackets around a one-line code block are optional).
The code block after the if instruction can be optionally followed by the
else keyword, followed in turn by a code block. The second code block will
be executed if the conditional expression returns false. The if instruction
corresponds to the natural language conditional sentence: "If this is true,
do this, else do that".
The else keyword can also be followed by another if instruction. In this
case the code that follows will be executed only if the conditional
expression in the second if statement returns true.
The following example clarifies the concept.
<?
if (a == 0)
{
print("Warning! Null data.")
}
else if (a < 1 || a > 100)
{
print("Error! Values must be between 1 and 100.")
}
else
{
// Process value of variable a
}
?>
Notice how the condition expressed by the first if is more restrictive than
the one of the second. Also, the order of the two statements can not be
inverted, otherwise the code block for the case a == 0 will never be
executed.
Using a curly bracket to delimit a code block consisting of a single line
is optional. Biferno also features the ? ternary operator, also known from
the C language. This operator provides a concise syntax to specify simple
if-else instructions:
(expression 1) ? (expression 2) : (expression 3)
An example is:
x = (a > b) ? a : b
This is equivalent to:
if (a > b)
x = a
else
x = b
The code assigns to the x variable the largest of the values of the a and b
variables.
The if instruction allows to concatenate a sequence of logical tests by
using the if- else if-else construct. This technique can produce code that
is difficult to read and error-prone when it is necessary to execute a
different block of code for each of the many values that a variable can
assume. In this case is often preferable to use the switch instruction with
the following syntax:
switch (main expression)
{
case expression_1:
execute block 1
break
case expression_2:
execute block 2
break
...
case expression_n:
execute block n
break
default:
execute default block
}
The main expression is calculated first and its value is compared with the
values of the expressions following the case instruction, in the order in
which such expressions are written. If the value of the main expression is
equal to the value of one of these expressions, the code block between the
semicolon on the expression line and the end of the entire switch
instruction (delimited by the closing curly bracket) is executed. All
following case instructions are ignored. Limiting the execution to the
single code block corresponding to the comparison expression is possible by
ending the block with the break instruction, which causes execution to
resume with the first instruction following the switch statement.
If the comparison between the result of the main expression and the results
of the case expressions yields no match, no code block is executed, unless
the switch statement has a default clause (usually the last) followed by a
code block. This code block will be executed if the main expression does
not match any case expression. This mechanism is similar to the last else
instruction of the if-else if-else construct: if none of the logical tests
is true, then execute a default code block.
The following sample script translates and prints the names of the days of
the week from Italian into English. The script assumes that the day
variable of the string class has been initialized, e.g. by user input
(passing parameters to a Biferno script is discussed in Chapter 18, Passing Parameters between Pages).
<?
switch (day)
{
case "Lunedì":
print("Monday")
break
case "Martedì":
print("Tuesday")
break
case "Mercoledì":
print("Wednesday")
break
case "Giovedì":
print("Thursday")
break
case "Venerdì":
print("Friday")
break
case "Sabato":
print("Saturday")
break
case "Domenica":
print("Sunday")
break
default:
print("Error! The string does not \
contain the name of a day of the week")
}
?>
Expressions following the case instructions must be static constants
properties of a class, or literal expressions. They can never be object (of type var or const).
Loops allow to repeat the execution of a code block depending on a logical
condition. All iterative control loop instructions present in other
languages are implemented in Biferno: for, while and do-while. The
fundamental difference between a for and a while loop is in the way the
number of iterations is defined and controlled. Usually, for loops are used
when the number of iterations to be executed can be determined a priori.
while loops are used when the number of iterations to be executed can not
be determined in advance, but depends on the value of one or more logical
conditions.
The for instruction has the following base syntax:
for (initial value; control condition; increment)
{
code block to be repeated
}
The instructions between round brackets usually consist in the initialization of an integer index, followed by an expression that must be verified (i.e. assume the true value) to allow continuation of the loop execution, followed by an expression that increments (or decrements) the index value.
The following example shows a simple for loop that calculates the sum of
the integers between 1 and 10.
<?
sum = 0
for (i = 1; i <= 10; i++)
{
sum += i
}
?>
Outside of the loop the integer variable sum is initialized to 0. The loop
index i is initialized to 1. The control condition verifies if i is less or
equal to 10 to continue the loop. With every iteration of the loop, the
current value of the i index is added to the sum variable. At the end of
the loop execution the value of the sum variable will be 55, i.e. we have
executed the following expression:
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 = 55.
Notice that after the last iteration of the loop the value of the i index
will be 11, because this is the first value that causes the control
condition i <= 10 to assume the value false, which stops the loop.
More than one element can be specified in a single for instruction for the
initial value and the increment. We can write:
<?
for (i = 1, j = 10; j > 0; i += j, j--)
{
print(i + ":" + j)
if (j > 1)
print (", ")
}
?>
This script produces the following result:
1:10, 11:9, 20:8, 28:7, 35:6, 41:5, 46:4, 50:3, 53:2, 55:1
The while instruction has the following syntax:
while (control condition)
{
code block to be repeated
}
The control condition between round brackets must be verified (i.e. assume
the true value) to allow execution of the code block between curly
brackets.
The value of the expression implementing the control condition is computed
before each repetition of the loop. This implies that the loop code block
is never executed if the expression is initially false.
For the same reason the loop code block must contain instructions that
modify the value of the expression used as the control condition (or the
break instruction, as we will see), otherwise the exit condition might
never become true, producing an infinite loop (in Biferno the execution of
a script is subject to a time limitation, after which it is forcibly
stopped).
A simple example of a while loop printing the integer numbers from 1 to 10
is:
<?
i = 1
while (i <= 10)
{
print(i + "<br>\n")
i++
}
?>
It is easy to see that, unlikely the for loop, the i variable is
initialized outside of the loop and incremented inside the loop block code.
In this way, when the i variable assumes the value 11, the condition i <= 10 is false and the loop is interrupted.
The do-while instruction has the following syntax:
do
{
code block to be repeated
}
while(control condition)
The fundamental difference between the do-while and the while instructions
is that in the do-while the control of the condition is performed at the
end of each repetition. This implies that the code block to be repeated is
always executed at least once.
The use of the break instruction to interrupt the execution of a code block
has been seen with respect to the switch instruction. In general, the break
instruction can be used to forcibly exit a loop and continue the execution
of the code starting from the first instruction after the loop block.
Sometimes it can be necessary to interrupt the execution of a script before
all its instructions have been executed. In Biferno this is realized via
the stop and exit instructions. The two instructions are different. The
exit instruction interrupts the execution of the entire script. The stop
instruction interrupts the execution of the ".bfr" file which is being
processed by the server when the stop instruction is executed. If the
current file has been included in another Biferno file using the include
instruction (see the Section 8, “The Include Instruction”), processing will resume
with the instruction following the include directive in the including file.
The continue instruction can be used to interrupt the execution of the
current iteration of a loop. This instruction immediately interrupts
execution of the current iteration and restarts execution from the first
instruction of the loop. If the current iteration is the last one,
execution resumes from the instruction following the loop code block that
has called the continue instruction. Execution of the continue instruction
outside of a loop generates the Err_IllegalUseOfKeyword error.
This instruction is actually a predefined Biferno function that takes a
file path as a parameter. When this instruction is executed in a script,
execution resumes with the first instruction of the included file. If the
included file is a Biferno script, its instructions are executed as if they
would have been contained in the including file. When execution of the
included file terminates, processing continues with the first instruction
following the include instruction in the including script. For this reason,
if a file to be included contains Biferno code, the code must always be
delimited by the <? and ?> tags as if the code would be in a stand
alone script.
To better understand the include function we show an example of its use
within a for loop. Assume we have written three files to be included as
follows. The "include1.bfr" file contains the following code:
<?
s = "include1" // initialize s variable of class string
?>
The "include2.bfr" file contains the following code:
<?
s += " include2" // concatenate a string with variable s
?>
The "include3.bfr" file contains the following code:
<?
s += " include3" // concatenate another string with variable s
?>
Our main script contains a loop that includes all three files in sequence. The name of the file to be included at each iteration is constructed using the loop index.
<?
for (i = 1; i <= 3; i++)
{
include("include" + i + ".bfr")
}
?>
When execution of this script terminates, the s variable has the value: "include1 include2 include3".
This example shows how the include function implements branching in the
execution of a script. The use of this function avoids the repetition of
code blocks that have to be executed many times in different scripts.
This instruction allows to stop execution of a script and to resume it in a
different point within the same script or code block within a single file.
The goto instruction can not be used to jump to a given position in a file
included via the include instruction. The goto instruction has the
following syntax:
goto label
where “label” identifies the code line where processing must resume. To label
a code line we write the label name followed by the ":" character
(semicolon). A simple example is the following script that prints the
integer numbers from 1 to 10 separated by a space:
<?
i = 1
start:
if (i > 10)
goto end
print(i++ + " ")
goto start
end:
?>
When the goto start instruction is executed, the execution stops and is
resumed with the instruction following the start label, i.e. the if
instruction. Similarly, the goto end instruction causes a jump to the line
following the end label, i.e. the end of the script. It should be apparent
that this script can be written in a much more elegant and natural way
using the for loop.
It has been proved that every program containing goto instructions can be
rewritten using only the fundamental control structures. Moreover, the use
of the goto instruction makes it difficult to understand program structure.
We advise to avoid using this instruction, which has been implemented in
Biferno only for reasons of completeness.
Usually Biferno scripts are executed concurrently, i.e. it is possible that two or more users request the same script to the server and that the scripts is executed in parallel by different Biferno threads. Biferno is a multi-threaded application, i.e. capable to start multiple execution instances within a single process.
The default behavior can be avoided if necessary by isolating code segments that should never be executed concurrently by multiple users.
This is accomplished in Biferno by using the lock and unlock instructions.
The script code enclosed by the lock and unlock is executed by Biferno for
a single user at a time. All other execution requests relative to that code
segment are queued and will be executed one by one in sequence.
The lock instruction is incremental in that it can be used multiple times
within the same script, as long as there is a matching unlock instruction
for every lock instruction. In any case, to avoid server blocking, Biferno
calls automatically the unlock instruction for every lock that is still
unbalanced when the script execution terminates. The lock and unlock
instructions are often used to preserve data integrity when accessing files
or database in read/write mode.