• Reference Guide

for Cycle

for(Type flag ctl_var = DOMAIN FILTER INIT_VAL)
for(Type flag ctl_var = DOMAIN FILTER INIT_VAL)
for(Type flag ctl_var = DOMAIN FILTER INIT_VAL)
DOMAIN is one of the following:
N0, N1..Nlimit
N0..Nlimit by step
FILTER is either empty or of the form:
& filter-cond
INIT_VAL is either empty or of the form:
, init_val_expr
The for cycle first creates a control variable of the name specified by the identifier ctl_var. Values from the specified domain are successively assigned to this variable. For each assigned value, the block (the cycle body) is executed. The cycle body is thus evaluated as many times as many elements are there in the domain.
The value returned by the whole cycle equals to the value returned by the last evaluation of the block. If the domain is empty (i.e. the block has not been evaluated at all), the initial value of the block’s type is returned. E.g. if the block result is an integer number, the result would be 0. If an initial value is specified (the parameter init_val_expr), the value of the block (and thus the value of the whole cycle) is of the same type as the initial value is. See the section Aggregation below. If the until-branch is specified, the result is determined by its block2 (or block3 of the else-branch). See the section Search below.

The Domain

The domain is either a sequence of numbers (integer or real) or a value of the sequence data type. Besides of numbers, a domain of characters (the type Char) can be constructed. A numerical sequence is constructed as an interval specified by its first and last values (N0..Nlimit). In this case, the numbers are iterated one by one beginning with the N0 until Nlimit is reached (including the value Nlimit itself). If Nlimit is less than N0, the cycle is not executed at all. If the value N1 is specified in addition to N0¸ the step is determined from the difference N1–N0. If N1 is greater than Nlimit, neither value N1 nor Nlimit is iterated in the cycle. The step can also be specified using the clause by.
Domain Examples:
5..10 5, 6, 7, 8, 9, 10
10..5 by -1 10, 9, 8, 7, 6, 5
0, 2..10 0, 2, 4, 6, 8, 10
0, 2..9 0, 2, 4, 6, 8
0, –1..10 empty domain; the cycle is not passed even once.
1, 1.1..2 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0,
10..0 by –2 10, 8, 6, 4, 2, 0
'a'..'e' by 2 'a', 'c', 'e'
In the case of domain of a sequence data type, a reference to a sequence (or a table) in the document data determines the domain. Also a constant of the sequence data type (e.g. {"January", "February", "March"}), or of course an arbitrary expression returning a sequence can be used to specify the domain.
Even an OLE-object, provided it is a collection, can be used as a cycle domain.

Control Variable

The Type expression specifies the type of the control variable. If the type can be inferred from the domain type, it is not necessary to specify it explicitly. This is especially useful when the cycle iterates through table rows, as the type of the row can be a complex structure.
The name of the control variable can be preceded by the symbol & (the syntax element flags) which makes the variable to iterate through the domain by reference rather than by value. That means that a reference to the element being iterated is assigned to the control variable, rather than the value of the element itself. This option can be used only for domains that are themselves specified by a reference to a data object (by a (l-value). The advantage of iteration by reference is twofold. First, the element values can be changed during the iteration. Second, for complex tables, this method is more efficient, because it is not necessary to copy all the row data from the table into the control variable.
for(&element = document.data.employees)
element.wage *= 1.15
Here, for the employees with higher performance than 10, their wage is increased by 15 %.

Using Filter

The cycle domain can be further modified by a filter. The filter is written immediately after the domain specification, separated by the & (ampersand) sign. The filter is a condition (an expression of the Bool type), which determines whether the cycle body is to be executed for the element in question. For each domain element, the condition is evaluated first. If the result is FALSE, the body is not evaluated, and the execution proceeds to the next domain element, instead.
The advantage of using a filter as opposed to an if-condition inside the cycle body is in aggregation (described below) and in proper determination of the last pass by the operator is_last_pass.
for(&element = document.data.employees & element.performance>10)
element.wage *= 1.15
This example is equivalent to the previous one.


One of the typical usage of a cycle is value aggregation. This includes for instance computing of a sum, minimum, selecting elements with desired property, etc. The Enki language enables easy computation of aggregated values using the for cycle. For this purpose, the symbol @ctl_var (the name of the control variable preceded with the at-sign) is introduced. This symbol denotes the value returned by the body in the previous pass. (The elements skipped by filter do not count). As using this symbol within the body leads effectively to adding another value to the previous one, it is possible to view this symbol as a value accumulator.
In the first pass, the accumulator is set to the initial value of the type of the body. Since it can be difficult to determine exact type of the body in certain cases, and also because the initial value of the type is not necessarily the desired initial value for the accumulator, it is possible to specify the initial value of the accumulator explicitly in the cycle header right after the domain. The accumulator initial value is separated by comma. If it is necessary to specify the type of the initial value (and thus to enforce the accumulator type), the type cast operator can be used.
Example 1:
var Int summ = for(i = 1..10) (@i+i)
This example computes the sum of the numbers from one to ten.
Example 2:
var Real mw = for(&element = document.data.employees)
(max(@element, element.wage))
This example computes the maximum wage of the employees.
Example 3:
var Int factorial = for(i = 1..10, 1) (@i*i)
This example computes 10! (factorial). Because of multiplication, the accumulator value must be preset to 1.
Example 4:
var String[*] list = for(&element = document.data.employees &
element.wage > 2000,
(@element # element.name)
This example creates a list of employees with wage above 2000. The clause [String[*]]{} sets initial accumulator value to an empty list (sequence) of strings. New elements are added to the list by the operator of sequence concatenation #.


Another typical usage of a cycle is searching. In order to facilitate searching, an until-branch (with an optional else-branch) is avalable. If an until-branch is specified, the condition cond of the until-branch is evaluated after every pass. If the condition is satisfied (the expression returns TRUE), the cycle is terminated, the block2 of the until-branch is evaluated, and its result is returned as the result of the entire for cycle. In other words, the until-branch condition determines what is looked for, while the block2 determines what to return, when desired element is reached. If the until-branch is specified, but its condition is not met for any element, the block3 of the else-branch is evaluated after the last pass and its value is returned by the for cycle. Thus, the else-branch determines result value for the case, when the search fails. If the until-branch is present, but the else-branch is missing, and the search fails, the initial value of the until-branch type is returned.
If no cycle body is needed, it is possible to omit the block altogether.
It is possible to combine searching with aggregation. The accumulator symbol can be also used within both condition and blocks of until- and else-branches.
Example 1:
var String name = for(&element = document.data.employees)
This example sets the name variable to the first_name of the first employee found in list whose name is Smith. The else-branch could be omitted, because when the search failed, the result would be the initial value of the type returned by the until-branch, which is an empty string in this case.
Example 2:
var Int[*] primes = for(i=2..1000 & for(j=@i)
until(i mod j==0)
[Int[*]]{}) (@i#i);
This computes list of all prime numbers less than 1000. The list of primes is aggregated in the accumulator of the outer cycle – the last line contains definition of the accumulator initial value as an empty list of integers. The body of the outer cycle adds a new found element to the accumulator (with the # (Sequence Concatenation) operator). Another cycle is used as a filter of the outer cycle. It searches among the prime numbers found so far a divisor of the current candidate number. The domain of the inner cycle is thus the accumulator of the outer cycle. If a divisor is found, the filter value is FALSE, and therefore the candidate number of the outer cycle is skipped, otherwise added to the list.

Error Handling

If the result of a condition or a block evaluation is an error (the Error type), the cycle is terminated, and its result is the error. For details, see the chapter on error handling. If the cycle body manipulates with the Error type, it is necessary to ensure that the Error value is not the body result. The try statement is suitable for this purpose.


The expressions block, block2, and block3 are blocks in the sense that the scope of variables defined within them is limited to the block.
The cycle can be prematurely terminated by the break statement.
Within the for cycle, the function pass_count can be used, which returns number of passes executed so far, the function is_firts_pass, which determines whether the first pass is being executed, and the function is_last_pass, which determines whether the last pass is being executed.
Iterative execution not restricted by a domain can be realized using the while cycle.