Loosely speaking, a statment is composed of expressions that are grouped according to the syntax or grammar of the language to express a complete computation. Statements are analogous to sentences in a human language and expressions are like phrases. All statements in the S-Lang language must end in a semi-colon.
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 statments such as assignment statements, conditional statements, and so forth. These are described in detail in the following sections.
Variable declarations were already discussed in chapter ???. 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 assignement can be performed. Such an object is called an lvalue.
The most common assignment operator is the simple assignment
operator =
. Simple 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 assignment operators +=
and -=
.
Internally, S-Lang transforms
a += b;
to
a = a + b;
Similarly, a -= b
is transformed to a = a - b
. It is
extremely important to realize that, in general, a+b
is not
equal to b+a
. This means that a+=b
is not the same
as a=b+a
. As an example consider
a = "hello"; a += "world";
After executaion of these two statements, a
will have the
value "helloworld"
and not "worldhello"
.
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 section ???.
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 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 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
statment 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 returns non-zero,
statement-or-block-1 will get executed and control will pass
on to next-statement. However, if expression returns zero,
statement-or-block-2 will get executed before continuing with
next-statement. A simple example of this form is
if (x > 0) z = log (x); else error ("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. The grammar presented above shows that
the 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;
}
}
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 !if
statement was added to the language to simplify the
handling of such statemnts. It obeys the syntax
!if (integer-expression) statement-or-block
and is functionally equivalent to
if (not (expression)) statement-or-block
These constructs were discussed earlier. 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 }
returns 0
since the third block will be the last to execute.
The switch statement deviates the most from its C counterpart. The syntax is:
switch (x)
{ ... : ...}
.
.
{ ... : ...}
The `:
' operator is a special symbol which means to test
the top item on the stack, 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 : print("Number is one.");}
{ x == 2 : print("Number is two.");}
{ x == 3 : print("Number is three.");}
{ x == 4 : print("Number is four.");}
{ x == 5 : print("Number is five.");}
{ print ("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 used the
case
keyword:
switch (x)
{ case 1 : print("Number is one.");}
{ case 2 : print("Number is two.");}
{ case 3 : print("Number is three.");}
{ case 4 : print("Number is four.");}
{ case 5 : print("Number is five.");}
{ print ("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") : print("Number is one.");}
{ (x == 2) or (x == "two") : print("Number is two.");}
{ (x == 3) or (x == "three") : print("Number is three.");}
{ (x == 4) or (x == "four") : print("Number is four.");}
{ (x == 5) or (x == "five") : print("Number is five.");}
{ print ("Number is greater than five.");}
will fail because the ==
operation is not defined between
strings and integers. The correct way to write this to use the
case
keyword:
switch (x)
{ case 1 or case "one" : print("Number is one.");}
{ case 2 or case "two" : print("Number is two.");}
{ case 3 or case "three" : print("Number is three.");}
{ case 4 or case "four" : print("Number is four.");}
{ case 5 or case "five" : print("Number is five.");}
{ print ("Number is greater than five.");}
The while
statement follows the syntax
while (integer-expression) 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 ();
}
will loop forever since i
will never be 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 ();
The fact is that expressions such as i--
do not return a
value in S-Lang as they do in C. If you must write this way, use
the comma operator as in
i = 10;
while (i, i--) newline ();
The do...while
statment follows the syntax
do
statement-or-block
while (integer-expression);
The main difference between this statment 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 statment is the for
statement; nevertheless, it is a favorite of many programmers.
This statment obeys the syntax
for (init-expression; integer-expression; end-expression)
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 returns 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 statment 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 apparant complexity of the for
statement, it is
very easy to use. As an example, consider
sum = 0;
for (i = 1; i <= 10; i++) sum += 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
next-statment
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 cause the function newline
to get called 10 times.
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
A trivial example of this statement is
n = 10;
forever
{
if (n == 0) break;
newline ();
n--;
}
S-Lang also includes the non-local transfer functions 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.