Skip to content

Language Specification (6) : Statements

Benjamin Kowarsch edited this page Jan 12, 2024 · 13 revisions

Statements

A statement represents a computation that causes a transformation of the computational state of a program. Statements are used for their effects only, they do not return values and they may not occur within expressions.

The Assignment Statement

The assignment statement is a value-mutator statement. It assigns a given value to a mutable value.

assignment := targetDesignator ’:=’ expression ;

The designator is called the L-value and must be mutable. The expression is called the R-value and must be assignment compatible to the L-value. If these conditions are not met, a compile time error will occur. Assignment of constants NIL, 0u0 or 0 and calls to procedures containing such assignments shall be considered intentional and shall not be subjected to dead store elimination!

The INC/DEC Statement

The INC/DEC statement is a value-mutator statement. It consists of a designator followed by a mutator symbol, where ++ increments and -- decrements the designator by one. The designator is an L-value. It must be mutable and of a whole number type. If these conditions are not met, a compile time error will occur.

incOrDecStatement := designator ( ’++’ | ’--’ );

The COPY Statement

The COPY statement is a value-mutator statement. It copies values across type boundaries where types have a common or compatible value type.

copyStatement := COPY targetDesignator ’:=’ expression ;

The designator is called the L-value, the expression is called the R-value. The L-value must be mutable. The R-value must be copy compatible to the L-value. Both must be non-elementary types. If these conditions are not met, a compile time error will occur.

Copying Sets

Variables of different set types are not assignment compatible but they are copy compatible if their value types match or one is an extension or subtype of the other.

Given the declarations

TYPE BaseColour = ( Red, Green, Blue ); ExtColour = ( +BaseColour, Orange, Magenta, Cyan );
TYPE BaseColours = SET OF BaseColour; ExtColours = SET OF ExtColour;
VAR baseColours : BaseColours; extColours : ExtColours;

a statement of the form

COPY extColours := baseColours

copies set baseColours to set extColours

Copying Arrays

Variables of different array types are not assignment compatible but they are copy compatible if their value types match or one is an extension or subtype of the other.

Given the declarations

TYPE PosInt = [0..TMAX(INTEGER)] OF INTEGER;
TYPE ArrayA = ARRAY 20 OF INTEGER; ArrayB = ARRAY 10 OF PosInt;
VAR arrayA : ArrayA; arrayB : ArrayB;

a statement of the form

COPY arrayA := arrayB

copies array arrayB to array arrayA

a statement of the form

COPY arrayB := arrayA

copies array arrayA to array arrayB

Copying Between Sets and Arrays

Variables of array and set types are not assignment compatible with each other but they are copy compatible if their value types are copy compatible.

Given the declarations

TYPE Token = ( Unknown, Alias, And, Arglist, Array, Begin ... );
TYPE TokenSet = SET OF Token; TokenArray = ARRAY 100 OF Token;
VAR set : TokenSet; array : TokenArray;

a statement of the form

COPY set := array

copies the tokens stored in array to set

a statement of the form

COPY array := set

copies the tokens stored in set to array

The Procedure Call Statement

A procedure call statement is used to invoke a procedure. It consists of a designator that designates the called procedure, optionally followed by a list of parameters enclosed in parentheses to be passed to the procedure. Parameters passed are called arguments or actual parameters, those defined in the procedure’s header are called formal parameters.

In every procedure call, the types of actual and formal parameters must match. If they don’t, a compile time error will occur. Procedure calls may be recursive, that is, a procedure may call itself within its body. Recursive calls shall be optimised by tail call elimination (TCE), except when generating source code in a target language that does not support TCE.

The RETURN Statement

The RETURN statement is a flow-control statement used within a procedure body to return control to its caller and in the main body of the program to return control to the operating environment that activated the program.

Depending on the type of procedure in which a RETURN statement is used, it may or may not return a value. When returning from a regular procedure, no value may be returned. When returning from a function procedure a value of the procedure’s return type must be returned. Non-compliance will cause a compile time error.

The NEW Statement

The NEW statement is a memory management statement. It allocates storage for a new instance of the target type referenced by the type of its argument. Its syntax is

newStmt := NEW designator ( ’:=’ initValue | CAPACITY expression )? ;

where designator must be an L-value and initValue must be an R-value.

A statement of the form

NEW p

allocates a new instance of the target type of pointer p and passes a reference to the new instance in p

Allocation with Initialisation

By default, a newly allocated instance is not initialised. To initialise a new instance during its allocation, an initialisation value may be specified within a NEW statement.

A statement of the form

NEW array := { a, b, c }

allocates a new instance, initialises it with values a , b and c , and passes a reference to it in array

Allocation of Instances of Indeterminate Type

A capacity value may be specified to initialise a new instance of an indeterminate type.

A statement of the form

NEW buffer CAPACITY 1000

allocates a new instance of an indeterminate type with a capacity limit of 1000 values of the type’s indeterminate field and passes a reference to the new instance in buffer

The RETAIN Statement

The RETAIN statement is a memory management statement. It prevents premature deallocation of the target referenced by its argument. It is only supported with data types that provide a library binding to RETAIN.

The RELEASE Statement

The RELEASE statement is a memory management statement. It deallocates the target unless there are any prior invocations of RETAIN outstanding for the target. If so, RELEASE cancels one prior invocation of RETAIN.

The IF Statement

The IF statement is a flow-control statement that passes control to one of a number of blocks within its body depending on the value of a boolean expression. It evaluates a condition in form of a boolean expression. If the condition is TRUE then program control passes to its block. If the condition is FALSE and an ELSIF branch follows, then program control passes to the branch to evaluate that branch’s condition.

Again, if the condition is TRUE then program control passes to the block of the branch. If there are no ELSIF branches, or if the conditions of all ELSIF branches are FALSE, and if an ELSE branch follows, then program control passes to the block. At most one block in the statement is executed. IF statements must always be terminated with an END.

The CASE Statement

The CASE statement is a flow-control statement that passes control to one of a number of labeled statements or statement sequences depending on the value of an ordinal expression.

caseStatement := CASE expression OF ( ’|’ case )+ ( ELSE statementSequence )? END ;
case := caseLabels ( ’,’ caseLabels )* : StatementSequence ;
caseLabels := constExpression ( .. constExpression )? ;

Control is passed to the first statement following the case label that matches the ordinal expression. Case labels must be unique. There is no “fall through”. At the end of a label, control is passed to the first statement after the CASE statement. If no case label matches, control is passed to the ELSE block, or, if there is no ELSE block, to the first statement after the CASE statement.

The statement is also used as a type-guard to ensure type safety when addressing fields of a type extension of an extensible record whose exact type is unknown. In this case, the fields of a specific type extension are only addressable within a case label matching the specific extension type. Otherwise a compile time fault occurs.

CASE record OF (* record may be of any extension of the root type or the root type itself *)
| ExtnType : record^.extnField := value; (* addressing a field of a specific type extension *)

The LOOP Statement

The LOOP statement is a flow-control statement used to repeat a statement or statement sequence within its body indefinitely unless explicitly terminated by an EXIT statement within its body.

The WHILE Statement

The WHILE statement is a flow-control statement used to repeat a statement or statement sequence depending on a condition in form of a boolean expression. The expression is evaluated each time before the DO block is executed. The DO block is repeated as long as the expression evaluates to TRUE unless the statement is explicitly terminated by an EXIT statement within its body.

The REPEAT Statement

The REPEAT statement is is a flow-control statement used to repeat a statement or statement sequence depending on a condition in form of a boolean expression. The expression is evaluated each time after the REPEAT block has executed. The REPEAT block is repeated as long as the expression evaluates to TRUE unless the statement is explicitly terminated by an EXIT statement within its body.

The FOR Statement

The FOR statement is a flow-control statement that iterates over an iterable entity, executing a statement or statement sequence during each iteration cycle. It consists of a loop header and a loop body. The header consists of one or two loop variants, an optional descender, and an iterable expression. The body consists of a statement or statement sequence.

forStatement := FOR forLoopVariants IN iterableExpr DO statementSequence END ;

The Loop Variants

The loop variant section contains one or two identifiers through which the accessor of the iterated entity and its value are referenced within the loop.

forLoopVariants := accessor ( ’--’ )? ( ’,’ value )? ;
alias accessor, value = ident ;

The loop variant identifiers are declared by the loop header and they are only in scope within the header and body. Once a FOR statement has terminated, its loop variants are no longer in scope. The number of loop variants depends on the loop’s iterable expression.

  • If the iterable expression is an ordinal type, a subrange of an ordinal type or the designator of a set, then the accessor is also its value. The header thus contains only one loop variant, immutable within the loop body.
  • If the iterable expression is the designator of an array a, the header contains an accessor representing the iteration index i and an optional second loop variant v representing its value a[i]. Index i is always immutable within the loop body. The value is mutable if a is mutable, otherwise immutable.
  • If the iterable expression is the designator of a key-value dictionary d, the header contains an accessor representing the iteration key k and an optional second loop variant v representing the key’s value d[k]. Key k is always immutable within the loop body. The value is mutable if d is mutable, otherwise immutable.

During the first iteration cycle the loop variants reference that accessor, value or accessor/value pair which is first for the prevailing iteration order. Before each subsequent iteration cycle the loop variants are advanced to their successor for the prevailing iteration order. Iteration continues until all accessors, values or pairs have been visited unless the FOR statement is explicitly terminated by an EXIT statement within its body.

The Iteration Order

The actual order of values is determined by the type of the iterable. The default iteration order is ascending. For descending order, the first loop variant may be suffixed with a descender, denoted by the -- symbol.

The Iterable Expression

The iterable expression — or simply iterable — is denoted by (1) the identifier of an ordinal or subrange type, (2) an anonymous subrange thereof, or (3) the identifier of a set, array or key-value dictionary, or an array slice.

iterableExpr := qualident ordinalRange? ;

Iterating over Ordinal Types

If the iterable is an ordinal type or a subrange thereof, only one loop variant may be given. The loop variant is immutable. Its type is the ordinal type or subrange given and the loop iterates over all values of the iterable.

A statement of the form

FOR char IN CHAR DO
  WRITE char
END (* FOR *)

iterates over all values of type CHAR.

Given declaration

TYPE Colour = ( Red, Green, Blue )

a statement of the form

FOR colour IN Colour DO
  ...
END (* FOR *)

iterates over all enumerated values of type Colour.

The current value is referenced as colour within the loop.

A statement of the form

FOR value-- IN CARDINAL[1..99] DO
  WRITE value, " bottles of beer, take one down and pass it around.\n"
END (* FOR *)

iterates over subrange [1..99] of type CARDINAL in descending order.

Iterating over Collections

If the iterable is the designator of a set, only one loop variant may be given. The loop variant is immutable. Its type is the element type of the set and the loop iterates over all values in the set.

A statement of the form

FOR elem IN set DO...END

iterates over all elements stored in set.

If the iterable is the designator of an array or array slice, one or two loop variants may be given. The first is immutable and of type LONGCARD. The optional second is mutable if the array is mutable, otherwise immutable, and its type is the value type of the array. The loop iterates over all values stored in the array.

A statement of the form

FOR index, value IN array DO array[index] := value END;

iterates over all values of array.

A statement of the form

FOR index, value IN source[n..m] DO WRITE "source[", index, "] = ", value, "\n" END;

iterates over all values in array slice source[n..m].

If the iterable is the designator of a key-value dictionary, one or two loop variants may be given. The first is immutable and of the dictionary’s key type. The optional second is mutable if the dictionary is mutable, otherwise immutable, its type is the value type of the dictionary. The loop iterates over all key-value pairs stored in the dictionary.

A statement of the form

FOR key, value IN dict DO WRITE "dict[", key, "] = ", value, "\n" END;

iterates over all key-value pairs of dict.

The EXIT Statement

The EXIT statement is a control-flow statement used within the body of a LOOP, WHILE, REPEAT or FOR statement to terminate execution of the loop and transfer control to the first statement after its body. The EXIT statement may only occur within the body of loop statements. Non-compliance will cause a compile time error.

The READ Statement

The READ statement reads one or more values from a communications channel and transfers the values in a non-empty variadic list of designators. Its syntax is

readStmt := READ ( ’@’ chan ’:’ )? NEW? designator ( ’,’ designator )*

where chan is the designator of a communications channel.

A statement of the form

READ @file : foo, bar, baz;

reads three values from channel file and passes them in foo, bar and baz.

The communications channel may be omitted in which case a default input channel is used.

A statement of the form READ foo reads a value from the default input channel and passes it in variable foo.

The list of designators may be prefixed by reserved word NEW to allocate new memory for each value read. In this case every designator must designate a value of a pointer type and its value must be NIL.

A statement of the form

READ @file : NEW ptr;

reads a value from channel file, allocates a new instance of ptr and passes the read value in ptr^.

The WRITE Statement

The WRITE Statement statement writes one or more values to a communications channel. Its syntax is

writeStmt := WRITE ( ’@’ chan ’:’ )? outputArgs ( ’,’ outputArgs )* ;
outputArgs := formattedArgs | unformattedArgs ;
formattedArgs := ’#’ ’(’ fmtStr, expressionList ’)’ ;
alias unformattedArgs = expressionList ;

where chan is the designator of a communications channel and fmtStr is a format specifier string.

A statement of the form

WRITE @file : foo, bar, baz;

writes the values foo, bar and baz to channel file.

The communications channel may be omitted in which case a default output channel is used.

A statement of the form WRITE foo writes the value of foo to the default output channel.

The list of output values may include formatted and unformatted values. A formatted value or value list is preceded by a format specifier, enclosed in parentheses and preceded by #.

A statement of the form

WRITE @file : "Price: ", #("5:3;2", price), "incl. VAT\n";

writes three values to channel file applying format specifier "5:3;2" to value price.

Format specifiers are library defined but language specified for all built-in types, their interpretation takes place within the IO library. The syntax of format specifiers for built-in types is described in the Appendix.

The NOP Statement

The NOP statement represents an explicit empty statement.

The grammar forbids empty loops and blocks. When intended, the NOP statement must be used instead.

WHILE stream.nextChar() = ’ ’ DO (* skip all whitespace *)
  NOP
END; (* WHILE *)
Clone this wiki locally