Loosely speaking, a statement is composed of expressions that are grouped according to the syntax or grammar of the language to express a complete computation. A semicolon is used to denote the end of a statement.
A statement that occurs within a function is executed only during execution of the function. However, statements that occur outside the context of a function are evaluated immediately.
The language supports several different types of statements such as assignment statements, conditional statements, and so forth. These are described in detail in the following sections.
Variable declarations were already discussed in the chapter on Variables. For the sake of completeness, a variable declaration is a statement of the form
variable variable-declaration-list ;
where the variable-declaration-list is a comma separated list
of one or more variable names with optional initializations, e.g.,
variable x, y = 2, z;
Perhaps the most well known form of statement is the assignment statement. Statements of this type consist of a left-hand side, an assignment operator, and a right-hand side. The left-hand side must be something to which an assignment can be performed. Such an object is called an lvalue.
The most common assignment operator is the simple assignment
operator =
. Examples of its use include
x = 3;
x = some_function (10);
x = 34 + 27/y + some_function (z);
x = x + 3;
In addition to the simple assignment operator, S-Lang
also supports the binary assignment operators:
+= -= *= /= &= |=
Internally, S-Lang transforms
a += b;
to
a = a + b;
Likewise a-=b
is transformed to a=a-b
, a*=b
is
transformed to a=a*b
, and so on.
It is extremely important to realize that, in general, a+b
is not equal to b+a
. For example if a
and b
are strings, then a+b
will be the string resulting from the
concatenation of a
and b
, which generally is not he
same as the concatenation of b
with a
. This means
that a+=b
may not be the same as a=b+a
, as the
following example illustrates:
a = "hello"; b = "world";
a += b; % a will become "helloworld"
c = b + a; % c will become "worldhelloworld"
Since adding or subtracting 1 from a variable is quite
common, S-Lang also supports the unary increment and decrement
operators ++
, and --
, respectively. That is, for
numeric data types,
x = x + 1;
x += 1;
x++;
are all equivalent. Similarly,
x = x - 1;
x -= 1;
x--;
are also equivalent.
Strictly speaking, ++
and --
are unary operators. When
used as x++
, the ++
operator is said to be a
postfix-unary operator. However, when used as ++x
it is
said to be a prefix-unary operator. The current
implementation does not distinguish between the two forms, thus
x++
and ++x
are equivalent. The reason for this
equivalence is that assignment expressions do not return a value in
the S-Lang language as they do in C. Thus one should exercise care
and not try to write C-like code such as
x = 10;
while (--x) do_something (x); % Ok in C, but not in S-Lang
The closest valid S-Lang form involves a comma-expression:
x = 10;
while (x--, x) do_something (x); % Ok in S-Lang and in C
S-Lang also supports a multiple-assignment statement. It is discussed in detail in the section on Multiple Assignment Statement.
S-Lang supports a wide variety of conditional and looping statements. These constructs operate on statements grouped together in blocks. A block is a sequence of S-Lang statements enclosed in braces and may contain other blocks. However, a block cannot include function declarations. In the following, statement-or-block refers to either a single S-Lang statement or to a block of statements, and integer-expression is an integer-valued or boolean expression. next-statement represents the statement following the form under discussion.
The simplest condition statement is the if
statement. It
follows the syntax
if (integer-expression) statement-or-block
next-statement
If integer-expression evaluates to a non-zero (boolean TRUE)
result, then the statement or group of statements implied
statement-or-block will get executed. Otherwise, control will
proceed to next-statement.
An example of the use of this type of conditional statement is
if (x != 0)
{
y = 1.0 / x;
if (x > 0) z = log (x);
}
This example illustrates two if
statements where the second
if
statement is part of the block of statements that belong to
the first.
Another form of if
statement is the if-else statement.
It follows the syntax:
if (integer-expression) statement-or-block-1
else statement-or-block-2
next-statement
Here, if expression evaluates to a non-zero integer,
statement-or-block-1 will get executed and control will pass
on to next-statement. However, if expression evaluates to zero,
statement-or-block-2 will get executed before continuing on to
next-statement. A simple example of this form is
if (x > 0)
z = log (x);
else
throw DomainError, "x must be positive";
Consider the more complex example:
if (city == "Boston")
if (street == "Beacon") found = 1;
else if (city == "Madrid")
if (street == "Calle Mayor") found = 1;
else found = 0;
This example illustrates a problem that beginners have with
if-else statements. Syntactically, this example is equivalent to
if (city == "Boston")
{
if (street == "Beacon") found = 1;
else if (city == "Madrid")
{
if (street == "Calle Mayor") found = 1;
else found = 0;
}
}
although the indentation indicates otherwise. It is important to
understand the grammar and not be seduced by the indentation!
One often encounters if
statements similar to
if (integer-expression == 0) statement-or-block
or equivalently,
if (not(integer-expression)) statement-or-block
The ifnot
statement was added to the language to simplify the
handling of such statements. It obeys the syntax
ifnot (integer-expression) statement-or-block
and is functionally equivalent to
if (not (expression)) statement-or-block
Note: The ifnot
keyword was added in version 2.1 and is not
supported by earlier versions. For compatibility with older code,
the !if
keyword can be used, although its use is deprecated in
favor of ifnot
.
As of S-Lang version 2.1, use of the andelse
and
orelse
have been deprecated in favor of the &&
and
||
short-circuiting operators.
The syntax for the orelse
statement is:
orelse {integer-expression-1} ... {integer-expression-n}
This causes each of the blocks to be executed in turn until one of
them returns a non-zero integer value. The result of this statement
is the integer value returned by the last block executed. For
example,
orelse { 0 } { 6 } { 2 } { 3 }
returns 6 since the second block is the first to return a
non-zero result. The last two block will not get executed.
The syntax for the andelse
statement is:
andelse {integer-expression-1} ... {integer-expression-n}
Each of the blocks will be executed in turn until one of
them returns a zero value. The result of this statement is the
integer value returned by the last block executed. For example,
andelse { 6 } { 2 } { 0 } { 4 }
evaluates to 0 since the third block will be the last to execute.
The switch statement deviates from its C counterpart. The syntax is:
switch (x)
{ ... : ...}
.
.
{ ... : ...}
The `:
' operator is a special symbol that in the context of
the switch statement, causes the top item on the stack to be
tested, and if it is non-zero, the rest of the block
will get executed and control will pass out of the switch statement.
Otherwise, the execution of the block will be terminated and the process
will be repeated for the next block. If a block contains no
:
operator, the entire block is executed and control will
pass onto the next statement following the switch
statement.
Such a block is known as the default case.
As a simple example, consider the following:
switch (x)
{ x == 1 : message("Number is one.");}
{ x == 2 : message("Number is two.");}
{ x == 3 : message("Number is three.");}
{ x == 4 : message("Number is four.");}
{ x == 5 : message("Number is five.");}
{ message ("Number is greater than five.");}
Suppose x
has an integer value of 3. The first two
blocks will terminate at the `:
' character because each of the
comparisons with x
will produce zero. However, the third
block will execute to completion. Similarly, if x
is
7, only the last block will execute in full.
A more familiar way to write the previous example is to make use of
the case
keyword:
switch (x)
{ case 1 : message("Number is one.");}
{ case 2 : message("Number is two.");}
{ case 3 : message("Number is three.");}
{ case 4 : message("Number is four.");}
{ case 5 : message("Number is five.");}
{ message ("Number is greater than five.");}
The case
keyword is a more useful comparison operator because
it can perform a comparison between different data types while
using ==
may result in a type-mismatch error. For example,
switch (x)
{ (x == 1) or (x == "one") : message("Number is one.");}
{ (x == 2) or (x == "two") : message("Number is two.");}
{ (x == 3) or (x == "three") : message("Number is three.");}
{ (x == 4) or (x == "four") : message("Number is four.");}
{ (x == 5) or (x == "five") : message("Number is five.");}
{ message ("Number is greater than five.");}
will fail because the ==
operation is not defined between
strings and integers. The correct way to write this is to use the
case
keyword:
switch (x)
{ case 1 or case "one" : message("Number is one.");}
{ case 2 or case "two" : message("Number is two.");}
{ case 3 or case "three" : message("Number is three.");}
{ case 4 or case "four" : message("Number is four.");}
{ case 5 or case "five" : message("Number is five.");}
{ message ("Number is greater than five.");}
In this section, the various looping statements are discussed. Each
of these statements support an optional then
clause, which is
discussed in a separate section below.
The while
statement follows the syntax
while (integer-expression) statement-or-block
[ then statement-or-block ]
next-statement
It simply causes statement-or-block to get executed as long as
integer-expression evaluates to a non-zero result. For
example,
i = 10;
while (i)
{
i--;
newline ();
}
will cause the newline
function to get called 10 times.
However,
i = -10;
while (i)
{
i--;
newline ();
}
would loop forever (or until i
wraps from the most negative
integer value to the most positive and then decrements to zero).
If you are a C programmer, do not let the syntax of the language seduce you into writing this example as you would in C:
i = 10;
while (i--) newline ();
Keep in mind that expressions such as i--
do not return a
value in S-Lang as they do in C. The same effect can be achieved
to use a comma to separate the expressions as in
i = 10;
while (i, i--) newline ();
The do...while
statement follows the syntax
do
statement-or-block
while (integer-expression);
[ then statement-or-block ]
The main difference between this statement and the while
statement is that the do...while
form performs the test
involving integer-expression after each execution
of statement-or-block rather than before. This guarantees that
statement-or-block will get executed at least once.
A simple example from the jed editor follows:
bob (); % Move to beginning of buffer
do
{
indent_line ();
}
while (down (1));
This will cause all lines in the buffer to get indented via the
jed intrinsic function indent_line
.
Perhaps the most complex looping statement is the for
statement; nevertheless, it is a favorite of many C programmers.
This statement obeys the syntax
for (init-expression; integer-expression; end-expression)
statement-or-block
[ then statement-or-block ]
next-statement
In addition to statement-or-block, its specification requires
three other expressions. When executed, the for
statement
evaluates init-expression, then it tests
integer-expression. If integer-expression evaluates to zero,
control passes to next-statement. Otherwise, it executes
statement-or-block as long as integer-expression
evaluates to a non-zero result. After every execution of
statement-or-block, end-expression will get evaluated.
This statement is almost equivalent to
init-expression;
while (integer-expression)
{
statement-or-block
end-expression;
}
The reason that they are not fully equivalent involves what happens
when statement-or-block contains a continue
statement.
Despite the apparent complexity of the for
statement, it is
very easy to use. As an example, consider
s = 0;
for (i = 1; i <= 10; i++) s += i;
which computes the sum of the first 10 integers.
The loop
statement simply executes a block of code a fixed
number of times. It follows the syntax
loop (integer-expression) statement-or-block
[ then statement-or-block ]
next-statement
If the integer-expression evaluates to a positive integer,
statement-or-block will get executed that many times.
Otherwise, control will pass to next-statement.
For example,
loop (10) newline ();
will execute the newline
function 10 times.
Like loop
, the _for
statement simply executes a block of
code a fixed number times. Unlike the loop
statement, the
_for
loop is useful in situations where the loop index is
needed. It obeys the syntax
_for loop-variable (first-value, last-value, increment)
block
[ then statement-or-block ]
next-statement
Each time through the loop, the loop-variable will take on the
successive values dictated by the other parameters. The first time
through, the loop-variable will have the value of first-value.
The second time its value will be first-value +
increment, and so on. The loop will terminate when the value
of the loop index exceeds last-value. The current
implementation requires the control parameters first-value,
last-value, and increment to be integer-valued
expressions.
For example, the _for
statement may be used to compute the sum
of the first ten integers:
s = 0;
_for i (1, 10, 1)
s += i;
The execution speed of the _for
loop is more than twice as fast as
the more powerful for
loop making it a better choice for many
situations.
The forever
statement is similar to the loop
statement
except that it loops forever, or until a break
or a
return
statement is executed. It obeys the syntax
forever statement-or-block
[ then statement-or-block ]
A trivial example of this statement is
n = 10;
forever
{
if (n == 0) break;
newline ();
n--;
}
The foreach
statement is used to loop over one or more
statements for every element of an object. Most often the object
will be a container object such as an array, structure, or
associative arrays, but it need not be.
The simple type of foreach
statement obeys the syntax
foreach var (object) statement-or-block
[ then statement-or-block ]
Here object can be an expression that evaluates to a value.
Each time through the loop the variable var will take on a
value that depends upon the data type of the object being
processed. For container objects, var will take on values of
successive members of the object.
A simple example is
foreach fruit (["apple", "peach", "pear"])
process_fruit (fruit);
This example shows that if the container object is an array, then
successive elements of the array are assigned to fruit
prior to
each execution cycle. If the container object is a string, then
successive characters of the string are assigned to the variable.
What actually gets assigned to the variable may be controlled via the
using
form of the foreach
statement. This more complex
type of foreach
statement follows the syntax
foreach var ( container-object ) using ( control-list )
statement-or-block
The allowed values of control-list will depend upon the type
of container object. For associative arrays (
Assoc_Type
),
control-list specifies whether keys, values, or both
are used. For example,
foreach k (a) using ("keys")
{
.
.
}
results in the keys of the associative array a
being
successively assigned to k
. Similarly,
foreach v (a) using ("values")
{
.
.
}
will cause the values to be used. The form
foreach k,v (a) using ("keys", "values")
{
.
.
}
may be used when both keys and values are desired.
Similarly, for linked-lists of structures, one may walk the list via code like
foreach s (linked_list) using ("next")
{
.
.
}
This foreach
statement is equivalent
s = linked_list;
while (s != NULL)
{
.
.
s = s.next;
}
Consult the type-specific documentation for a discussion of the
using
control words, if any, appropriate for a given type.
S-Lang also includes the non-local transfer statements
return
, break
, and continue
. The return
statement causes control to return to the calling function while
the break
and continue
statements are used in the
context of loop structures. Consider:
define fun ()
{
forever
{
s1;
s2;
..
if (condition_1) break;
if (condition_2) return;
if (condition_3) continue;
..
s3;
}
s4;
..
}
Here, a function fun
has been defined that contains a forever
loop consisting of statements s1
, s2
,...,s3
, and
three if
statements. As long as the expressions condition_1
,
condition_2
, and condition_3
evaluate to zero, the statements
s1
, s2
,...,s3
will be repeatedly executed. However,
if condition_1
returns a non-zero value, the break
statement
will get executed, and control will pass out of the forever
loop to
the statement immediately following the loop, which in this case is
s4
. Similarly, if condition_2
returns a non-zero number,
the return
statement will cause control to pass back to the
caller of fun
. Finally, the continue
statement will
cause control to pass back to the start of the loop, skipping the
statement s3
altogether.
As mentioned above, all the looping statements support an optional
then
clause. The statements that comprise this clause get
executed only when the loop has run to completion and was not
prematurely terminated via a break
statement. As an example,
consider the following:
count = 0;
max_tries = 20;
while (count < max_tries)
{
if (try_something ())
break;
count++;
% Failed -- try again
}
if (count == 20)
throw RunTimeError, "try_something failed 20 times";
Here, the code makes 20 attempts to perform some task (via the
try_something
function) and if not successful it will throw
an exception. Compare the above to an equivalent form that makes
use of a then
-clause for the loop
statement:
max_tries = 20;
loop (max_tries)
{
if (try_something ())
break;
% Failed -- try again
}
then throw RunTimeError, "try_something failed 20 times";
Here, the then
statement would get executed only if the loop
statement has run to completion, i.e., loops 20 times in this case.
This only happens if the try_something
function fails each
time through the loop. However, if the try_something
function succeeds, then the break
statement will get executed
causing the loop to abort prematurely, which would result in the
then
clause not getting executed.
The use of such a construct can also simplify code such as:
if (some_condition)
{
foo_statements;
if (another_condition)
bar_statements;
else
fizzle_statements;
}
else fizzle_statements;
In this case the fizzle_statements
are duplicated making the
code ugly and less maintainable. Ideally one would wrap the
fizzle_statements
in a separate function and call it twice.
However, this is not always possible or convenient. The duplication
can be eliminated by using the then
form of the loop
statement:
loop (some_condition != 0)
{
foo_statements;
if (another_condition)
{
bar_statements;
break;
}
}
then fizzle_statements;
Here, the expression some_condition != 0
is going to result
in either 0 or 1, causing the code to execute 0 or 1 loops. Since
the fizzle_statements
are contained in the then
clause,
they will get executed only when the requested number of loops
executes to completion. Executing 0 loops is regarded as successful
completion of the loop statement. Hence, when some_condition
is 0, the fizzle_statements
will get executed. The
fizzle_statements
will not get executed only when the loop is
prematurely terminated, and that will occur when both
some_condition
and another_condition
are non-zero.