DragonFly On-Line Manual Pages
icistmt(1) DragonFly General Commands Manual icistmt(1)
NAME
icistmt - ICI statements syntax and semantics
DESCRIPTION
The if statement
The if statement has two forms:
if ( expression ) statement
if ( expression ) statement else statement
The parser converts both to an internal form. Upon execution, the
expression is evaluated. If the expression evaluates to anything other
than 0 (integer zero) or NULL, the following statement is executed;
otherwise it is not. In the first form this is all that happens, in
the second form, if the expression evaluated to 0 or NULL the statement
following the else is executed; otherwise it is not.
The interpretation of both 0 and NULL as false, and anything else as
true, is common to all logical operations in ici. There is no special
boolean type.
The ambiguity introduced by multiple if statements with a lesser number
of else clauses is resolved by binding else clauses with their closest
possible if. Thus:
if (a) if (b) do_x(); else do_y();
is equivalent to:
if (a)
{
if (b)
do_x();
else
do_y();
}
The while statement
The while statement has the form:
while ( expression ) statement
The parser converts it to an internal form. Upon execution a loop is
established. Within the loop the expression is evaluated, and if it is
false (0 or NULL) the loop is terminated and flow of control continues
after the while statement. But if the expression evaluates to true
(not 0 and not NULL) the statement is executed and then flow of control
moves back to the start of the loop where the test is performed again
(although other statements, as seen below, can be used to modify this
natural flow of control).
The do-while statement
The do-while statement has the following form:
do statement while ( expression ) ;
The parser converts it to an internal form. Upon execution a loop is
established. Within the loop the statement is executed. Then the
expression is evaluated and if it evaluates to true, flow of control
resumes at the start of the loop. Otherwise the loop is terminated and
flow of control resumes after the do-while statement.
The for statement
The for statement has the form:
for ( [ expression ]; [ expression ]; [ expression ] )
statement
The parser converts it to an internal form. Upon execution the first
expression is evaluated (if present). Then, a loop is established.
Within the loop: If the second expression is present, it is evaluated
and if it is false the loop is terminated. Next the statement is
executed. Finally, the third expression is evaluated (if present) and
flow of control resumes at the start of the loop.
The forall statement
The forall statement has the form:
forall ( expression [ ,expression ] in expression ) statement
The parser converts it to an internal form. In doing so the first and
second expressions are required to be lvalues (that is, capable of
being assigned to). Upon execution the first expression is evaluated
and that storage location is noted. If the second expression is
present the same is done for it. The third expression is then
evaluated and the result noted; it must evaluate to an array, a set, a
struct, a string, or NULL; we will call this the aggregate. If this is
NULL, the forall statement is finished and flow of control continues
after the statement; otherwise, a loop is established.
Within the loop, an element is selected from the noted aggregate. The
value of that element is assigned to the location given by the first
expression. If the second expression was present, it is assigned the
key used to access that element. Then the statement is executed.
Finally, flow of control resumes at the start of the loop.
Each arrival at the start of the loop will select a different element
from the aggregate. If no as yet unselected elements are left, the
loop terminates. The order of selection is predictable for arrays and
strings, namely first to last. But for structs and sets it is
unpredictable. Also, while changing the values of the structure
members is acceptable, adding or deleting keys, or adding or deleting
set elements during the loop will have an unpredictable effect on the
progress of the loop.
Note in particular the interpretation of the value and key for a set.
For consistency with the access method and the behavior of structs and
arrays, the values are all 1 and the elements are regarded as the keys.
As a special case, when the second expression is omitted, the first is
set to each "key" in turn, that is, the elements of the set.
When a forall loop is applied to a string (which is not a true
aggregate), the "sub-elements" will be successive one character sub-
strings.
Note that although the sequence of choice of elements from a set or
struct is at first examination unpredictable, it will be the same in a
second forall loop applied without the structure or set being modified
in the interim.
The switch, case, and default statements
These statements have the form:
switch ( expression ) compound-statement
case expression :
default :
The parser converts the switch statement to an internal form. As it is
parsing the compound statement, it notes any case and default
statements it finds at the top level of the compound statement. When a
case statement is parsed the expression is evaluated immediately by the
parser. As noted previously for parser evaluated expressions, it may
perform arbitrary actions, but it is important to be aware that it is
resolved to a particular value just once by the parser. As the case
and default statements are seen their position and the associated
expressions are noted in a table.
Upon execution, the switch statement's expression is evaluated. This
value is looked up in the table created by the parser. If a matching
case statement is found, flow of control immediately moves to
immediately after that case statement. On no match, if there is a
default statement flow of control immediately moves to just after that.
If there is no matching case and no default statement, flow of control
continues just after the entire switch statement.
The break and continue statements
The break and continue statements have the form:
break ;
continue ;
The parser converts these to an internal form. Upon execution of a
break statement the execution engine will cause the nearest enclosing
loop (a while, do, for or forall) or switch statement within the same
scope to terminate. Flow of control will resume immediately after the
affected statement. Note that a break statement without a surrounding
loop or switch in the same function or module is illegal.
Upon execution of a continue statement the execution engine will cause
the nearest enclosing loop to move to the next iteration. For while
and do loops this means the test. For for loops it means the step,
then the test. For forall loops it means the next element of the
aggregate.
The return statement
The return statement has the form:
return [ expression ] ;
The parser converts this to an internal form. Upon execution, the
execution engine evaluates the expression if it is present. If it is
not, the value NULL is substituted. Then the current function
terminates with that value as its apparent value in any expression it
is embedded in. It is an error for there to be no enclosing function.
The try statement
The try statement has the form:
try statement onerror statement
The parser converts this to an internal form. Upon execution, the
first statement is executed. If this statement executes normally flow
continues after the try statement; the second statement is ignored.
But if an error occurs during the execution of the first statement
control is passed immediately to the second statement.
Note that "during the execution" applies to any depth of function
calls, even to other modules or the parsing of sub-modules. When an
error occurs both the parser and execution engine unwind as necessary
until an error catcher (that is, a try statement) is found.
Errors can occur almost anywhere and for a variety of reasons. They
can be explicitly generated with the fail function (described below),
they can be generated as a side-effect of execution (such as division
by zero), and they can be generated by the parser due to syntax or
semantic errors in the parsed source. For whatever reason an error is
generated, a message (a string) is always associated with it.
When any otherwise uncaught error occurs during the execution of the
first statement, two things are done:
Firstly, the string associated with the failure is assigned to the
variable error. The assignment is made as if by a simple assignment
statement within the scope of the try statement.
Secondly, flow of control is passed to the statement following the
onerror keyword.
Once the second statement finishes execution, flow of control continues
as if the whole try statement had executed normally.
The handling of errors which are not caught by any try statement is
implementation dependent. A typical action is to prepend the file and
line number on which the error occurred to the error string, print
this, and exit.
The critsect statement
The critsect, or "critical section" statement has the form:
critsect statement
The parser converts this to an internal form. Upon execution, the
statement is executed indivisibly with respect to other threads. Thus:
critsect x = x + 1;
will increment x by 1, even if another thread is doing similar
increments. Without the use of the critsect statement we could
encounter a situation where both threads read the current value of x
(say 2) at the same time, then both added 1 and stored the result 3,
rather than one thread incrementing the value to 3, then the other to
4.
The indivisibility bestowed by a critsect statement applies as long as
the code it dominates is executing, including all functions that code
calls. Even operations that block (such as the waitfor statement) will
be affected. The indivisibility will be revoked once the critsect
statement completes, either through completing normally, or through an
error being thrown by the code it is dominating.
The waitfor statement
The waitfor statement has the form:
waitfor ( expression ; expression ) statement
The parser converts this to an internal form. Upon execution, a
critical section is established that extends for the entire scope of
the waitfor statement (except for the special exception explained
below). Within the scope of this critical section, the waitfor
statement repeatedly evaluates the first expression until it is true
(that is, neither 0 nor NULL). Once the first expression evaluates to
true, control passes to the statement (still within the scope of the
critical section). After executing statement the critical section is
released and the waitfor statement is finished.
However, each time the first expression evalutes to a false value, the
second expression is evaluated and the object that it evaluates to is
noted. Then, indivisibly, the current thread sleeps waiting for that
object to be signaled (by a call to the wakeup() function), and the
critical section is suppressed (thus allowing other thread to run). The
thread will remain asleep until it is woken up by a call to wakeup()
with the given object as an argument. Each time this occurs, the
critical section is again enforced and the process repeats with the
evaluation and testing of the first expression. While the thread is
asleep it consumes no significant CPU time.
The null statement
The null statement has the form:
;
The parser may convert this to an internal form. Upon execution it will
do nothing.
Declaration statements
There are two types of declaration statements:
declaration
storage-class declaration-list ;
storage-class identifier function-body
storage-class
extern
static
auto
declaration-list identifier [ = expression ]
declaration-list , identifier [ = expression ]
That is, a comma separated list of identifiers, each with an optional
initialisation, terminated by a semicolon.
The storage class keyword establishes which scope the variables in the
list are established in. Note that declaring the same identifier at
different scope levels is permissible and that they are different
variables.
A declaration with no initialisation first checks if the variable
already exists at the given scope. If it does, it is left unmodified.
In particular, any value it currently has is undisturbed. If it does
not exist it is established and is given the value NULL.
A declaration with an initialisation establishes the variable in the
given scope and gives it the given value even if it already exists and
even if it has some other value.
Note that initial values are parser evaluated expressions. That is
they are evaluated immediately by the parser, but may take arbitrary
actions apart from that.
Abbreviated function declarations
As seen above there are two forms of declaration. The second:
storage-class identifier function-body
is the normal way to declare simple functions, and is a shorthand for:
storage-class identifier = [ func function-body ] ;
E.g.:
static sum(a, b) { return a + b; }
is a shorthand for:
static sum = [func (a, b) { return a + b; }];
SEE ALSO
ici(1), icinet(1), icioo(1), iciops(1), icisyn(1), icitypes(1),
iciex(1)
icistmt(1)