Next Previous Contents

8. Statements

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. 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 statements such as assignment statements, conditional statements, and so forth. These are described in detail in the following sections.

8.1 Variable Declaration Statements

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;

8.2 Assignment Statements

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 =. 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 execution 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 ???.

8.3 Conditional and Looping Statements

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.

Conditional Forms

if

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 statement is part of the block of statements that belong to the first.

if-else

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!

!if

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 statements. It obeys the syntax
!if (integer-expression) statement-or-block
and is functionally equivalent to
if (not (expression)) statement-or-block

orelse, andelse

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.

switch

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.");}

Looping Forms

while

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 ();
        }
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 ();
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 ();

do...while

The do...while statement follows the syntax

do statement-or-block while (integer-expression);
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.

for

Perhaps the most complex looping statement is the for statement; nevertheless, it is a favorite of many programmers. This statement 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 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

     sum = 0;
     for (i = 1; i <= 10; i++) sum += i;
which computes the sum of the first 10 integers.

loop

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-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 cause the function newline to get called 10 times.

forever

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--;
       }

foreach

The foreach statement is used to loop over one or more statements for every element in a container object. A container object is a data type that consists of other types. Examples include both ordinary and associative arrays, structures, and strings. Every time through the loop the current member of the object is pushed onto the stack.

The simple type of foreach statement obeys the syntax

foreach (container-object) statement-or-block
Here container-object can be an expression that returns a container object. A simple example is
     foreach (["apple", "peach", "pear"])
      {
         fruit = ();
         process_fruit (fruit);
      } 
This example shows that if the container object is an array, then successive elements of the array are pushed onto the stack prior to each execution cycle. If the container object is a string, then successive characters of the string are pushed onto the stack.

What actually gets pushed onto the stack may be controlled via the using form of the foreach statement. This more complex type of foreach statement follows the syntax

foreach ( 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 specified whether keys, values, or both are pushed onto the stack. For example,
     foreach (a) using ("keys") 
       {
          k = ();
           .
           .
       }
results in the keys of the associative array a being pushed on the list. However,
     foreach (a) using ("values")
       {
          v = ();
           .
           .
       }
will cause the values to be used, and
     foreach (a) using ("keys", "values")
       {
          (k,v) = ();
           .
           .
       }
will use both the keys and values of the array.

Similarly, for linked-lists of structures, one may walk the list via code like

     foreach (linked_list) using ("next")
       {
          s = ();
            .
            .
       }
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.

8.4 break, return, continue

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.


Next Previous Contents