14. Expressions

Expressions can only exist within a statement or another expression.

14.1. Table of Operator Precedence

The following is a table containing all of the precedences and associativities of the operators in Gazprea. Parentheses are not listed: they do not participate in the precedence relation and instead override it by grouping their contents into a new atom.

Precedence

Operators

Associativity

(Highest) 1

.

left

2

[] (indexing)

left

3

^

right

4

unary +, unary -, not

right

5

*, /, %, **

left

6

+, -

left

7

..

N/A

8

<, >, <=, >=

left

9

==, !=

left

10

and

left

11

or, xor

left

(Lowest) 12

||

right

The stream operators -> and <- are statement-level operators, not expression operators, so they do not appear in the table above. They bind more loosely than every operator listed, being effectively the very bottom of the precedence relation. An entire expression is evaluated before it is sent to or read from a stream (see Streams).

14.2. Generators

A generator may be used to construct either a one or two dimensional array. A generator always yields an array value whose size is settled at the moment the generator is evaluated and is fixed thereafter. A generator creates a value of a 1D array type when one iterator variable is used, and a 2D array type when two iterator variables are used. Supplying any other number of iterator variables is ill-formed: the compiler must emit a SyntaxError (see Errors). Higher-dimensional generators are a potential addition to a future revision of this specification.

The domain in a domain expression is any array-typed value: static arrays, dynamically-sized vectors, strings, and ranges all count.

A generator consists of either one or two domain expressions, and an additional expression on the right hand side of the bar (|). This additional expression is used to create the generated values. For example:

integer[10] v = [i in 1..10 | i * i];
/* v[i] == i * i */

integer[2][3] M = [i in 1..2, j in 1..3 | i * j];
/* M[i][j] == i * j */

v -> std_output;
'\n' -> std_output;
M -> std_output;

Output

[1 4 9 16 25 36 49 64 81 100]
[[1 2 3] [2 4 6]]

The expression to the right of the bar (|) is used to generate the value at the given index.

Let T be the type of the expression to the right of the bar (|). With one iterator variable ranging over a domain of size N, the result is a 1D array of size N with element type T. With two iterator variables ranging over domains of size N and M respectively, the result is a 2D array of size N x M with element type T. Generators may be nested, and may be used within domain expressions. For instance, the generator below is perfectly legal:

integer i = 7;

/* The domain expression should use the previously defined i */
integer[*] v = [i in [i in 1..i | i] | [i in 1..10 | i * i][i]];

/* v should contain the first 7 squares. */
v -> std_output;

Output

[1 4 9 16 25 36 49]

14.3. Domain Expressions

A domain expression consists of an identifier denoting an iterator variable and an expression that evaluates to an array type. Domain expressions can only appear within iterator loops and generators. A domain expression is a way of declaring a variable that is local to the loop or generator, that takes on values from the domain in order. The domain’s element type must be inferable, so an empty array literal yields a TypeError. The scope of the iterator variable (the left hand side of the declaration) is within the body of the generator or loop. The domain (the right hand side) is evaluated before any of the iterator variables are initialized, and therefore the scope of the domain is the one enclosing the iterator loop or generator.

For instance:

integer i = 7;

/* This will print 1234567 */
loop i in 1..i {
  i -> std_output;
}

Output

1234567

Iterator variables are initialized after their domain. In loops, re-initialization happens at the start of each execution of the loop’s body statement. Only generators may chain iterator variables using commas.

integer i = 2;

/* The "i"s in both domain expressions are at the same scope, which is
 * the one enclosing the generator. Therefore the matrix is: [[0 0 0] [0 1 2] [0 2 4]]
 */
integer[3][3] mat = [ i in 0..i, j in 0..i | i*j ];
mat -> std_output;

Output

[[0 0 0] [0 1 2] [0 2 4]]

The domain of a domain expression is only evaluated once. For instance:

integer x = 2;

/* 1..x is evaluated once, when control first reaches the loop, so it
   is simply 1..2 -- the two-element range [1, 2] -- and not an infinite
   loop. */
loop i in 1..x {
  x = x + 1;
}
x -> std_output;

Output

4

This is true for domain expressions within generators as well.

Because the domain is captured by evaluating it once, a runtime-sized domain fixes its iteration count at initialization. A vector or string may serve as the domain; the length it holds at the moment the domain is captured fixes the number of iterations, before the loop body executes.

A range domain may be empty. Because i..j has length max(0, j - i + 1) (see Operations), a domain such as 5..1 is the empty range, so a loop or generator over it simply iterates zero times, equivalent to using the empty array literal []:

loop i in 5..1 { i -> std_output; } /* 5..1 is empty: body runs 0 times */

Iterator variables can be assigned to and re-declared within the enclosed iterator loop. Neither carries information into the next iteration: the next iteration performs re-initialization from the captured domain, so any shadowing binding introduced by re-declaration is torn down and the iterator variable is bound fresh.

loop i in 1..6 {
  i -> std_output; // produces 123456
  integer i = 5;
}

loop i in 1..6 {
  integer i = 5;
  i -> std_output; // produces 555555
}