GP Operators and their Priorities

Loosely speaking, an operator is a function, usually attached to basic arithmetic operations, whose name contains only nonalphanumeric characters. For instance + or -, but also = or += , or even [ ] (the selection operator). As all functions, operators take arguments, and return a value; assignment operators also have side effects: besides returning a value, they change the value of some variable.

Each operator has a fixed and unchangeable priority, which means that, in a given expression, the operation with the highest priority is performed first. Unless mentioned otherwise, operators at the same priority level are left-associative (performed from left to right), unless they are assignments, in which case they are right-associative. Anything enclosed between parenthesis is considered a complete subexpression, and is resolved recursively, independently of the surrounding context. For instance,

    a + b + c    -->   (a + b) + c     \\  left-associative
    a = b = c    -->   a = (b = c)     \\  right-associative

Assuming that op1, op2, op3 are binary operators with increasing priorities (think of +, *, ^), x op1 y op2 z op2 x op3 y is equivalent to x op1 ((y op2 z) op2  (x op3 y)).

GP contains many different operators, either unary (having only one argument) or binary, plus a few special selection operators. Unary operators are defined as either prefix or postfix, meaning that they respectively precede (op x) and follow (x op) their single argument. Some symbols are syntactically correct in both positions, like !, but then represent different operators: the ! symbol represents the negation and factorial operators when in prefix and postfix position respectively. Binary operators all use the (infix) syntax x op y.

Most operators are standard (+, %, = ), some are borrowed from the C language (++, << ), and a few are specific to GP (\, #). Beware that some GP operators differ slightly from their C counterparts. For instance, GP's postfix ++ returns the new value, like the prefix ++ of C, and the binary shifts << , >> have a priority which is different from (higher than) that of their C counterparts. When in doubt, just surround everything by parentheses; besides, your code will be more legible.

Here is the list of available operators, ordered by decreasing priority, binary and left-associative unless mentioned otherwise. An expression is an lvalue if something can be assigned to it. (The name comes from left-value, to the left of a = operator; e.g. x, or v[1] are lvalues, but x + 1 is not.)

* Priority 14

: as in x:small, is used to indicate to the GP2C compiler that the variable on the left-hand side always contains objects of the type specified on the right hand-side (here, a small integer) in order to produce more efficient or more readable C code. This is ignored by GP.

* Priority 13 ( ) is the function call operator. If f is a closure and args is a comma-separated list of arguments (possibly empty), f(args) evaluates f on those arguments.

* Priority 12

++ and -- (unary, postfix): if x is an lvalue, x++ assigns the value x+1 to x, then returns the new value of x. This corresponds to the C statement ++x: there is no prefix ++ operator in GP. x-- does the same with x-1. These operators are not associative, i.e. x++++ is invalid, since x++ is not an lvalue.

* Priority 11

.member (unary, postfix): x.member extracts member from structure x (see Section se:member).

[ ] is the selection operator. x[i] returns the i-th component of vector x; x[i,j], x[,j] and x[i,] respectively return the entry of coordinates (i,j), the j-th column, and the i-th row of matrix x. If the assignment operator ( = ) immediately follows a sequence of selections, it assigns its right hand side to the selected component. E.g x[1][1] = 0 is valid; but beware that (x[1])[1] = 0 is not (because the parentheses force the complete evaluation of x[1], and the result is not modifiable).

* Priority 10

' (unary, postfix): derivative with respect to the main variable. If f is a function (t_CLOSURE), f' is allowed and defines a new function, which will perform numerical derivation when evaluated at a scalar x; this is defined as (f(x+ϵ) - f(x-ϵ)) / 2ϵ for a suitably small epsilon depending on current precision.

  ? (x^2 + y*x + y^2)'  \\  derive with respect to main variable x
  %1 = 2*x + y
  ? SIN = cos'
  %2 = cos'
  ? SIN(Pi/6)         \\  numerical derivation
  %3 = -0.5000000000000000000000000000
  ? cos'(Pi/6)        \\  works directly: no need for intermediate SIN
  %4 = -0.5000000000000000000000000000

~ (unary, postfix): vector/matrix transpose.

! (unary, postfix): factorial. x! = x(x-1)...1.

# (unary, postfix): primorial. For a non-negative integer n, n# is the product of all prime numbers less than or equal to n.

* Priority 9

# (unary, prefix): cardinality; #x returns length(x).

! (unary, prefix): logical not. !x returns 1 if x is equal to 0 (specifically, if gequal0(x) == 1), and 0 otherwise.

* Priority 8

^: powering. This operator is right associative: 2^3^4 is understood as 2^(3^4).

* Priority 7

+, - (unary, prefix): - toggles the sign of its argument, + has no effect whatsoever.

* Priority 6

*: multiplication.

/: exact division (3/2 yields 3/2, not 1.5).

\, %: Euclidean quotient and remainder, i.e. if x = qy + r, then x \ y = q, x%y = r. If x and y are scalars, then q is an integer and r satisfies 0 ≤ r < |y|; if x and y are polynomials, then q and r are polynomials such that deg r < deg y and the leading terms of r and x have the same sign.

\/: rounded Euclidean quotient for integers (rounded towards + oo when the exact quotient would be a half-integer).

<< , >> : left and right binary shift. By definition, x << n  =  x * 2^n if n > 0, and truncate(x 2-n) otherwise. Right shift is defined by x >> n  =  x << (-n).

* Priority 5

+, -: addition/subtraction.

* Priority 4

< , > , <= , >= : the usual comparison operators, returning 1 for true and 0 for false. For instance, x <= 1 returns 1 if x ≤ 1 and 0 otherwise.

<> , ! = : test for (exact) inequality.

== : test for (exact) equality.

=== : test whether two objects are identical component-wise. This is stricter than == : for instance, the integer 0, a 0 polynomial or a vector with 0 entries, are all tested equal by == , but they are not identical.

* Priority 3

&&: logical and.

||: logical (inclusive) or. Any sequence of logical or and and operations is evaluated from left to right, and aborted as soon as the final truth value is known. Thus, for instance,

    x == 0 || test(1/x)

will never produce an error since test(1/x) is not even evaluated when the first test is true (hence the final truth value is true). Similarly

    type(p) == "t_INT" && isprime(p)

does not evaluate isprime(p) if p is not an integer.

* Priority 2

= (assignment, lvalue = expr). The result of x  =  y is the value of the expression y, which is also assigned to the variable x. This assignment operator is right-associative. This is not the equality test operator; a statement like x  =  1 is always true (i.e. nonzero), and sets x to 1; the equality test would be x == 1. The right hand side of the assignment operator is evaluated before the left hand side.

It is crucial that the left hand-side be an lvalue there, it avoids ambiguities in expressions like 1 + x = 1. The latter evaluates as 1 + (x = 1), not as (1 + x) = 1, even though the priority of = is lower than the priority of +: 1 + x is not an lvalue.

If the expression cannot be parsed in a way where the left hand side is an lvalue, raise an error.

  ? x + 1 = 1
    ***   syntax error, unexpected '=', expecting $end or ';': x+1=1
    ***                                                           ^--

Assignment to all variables is a deep copy: after x = y, modifying a component of y will not change x. To globals it is a full copy to the heap. Space used by local objects in local variables is released when they go out of scope or when the value changes in local scope. Assigning a value to a vector or matrix entry allocates room for that entry only (on the heap).

op = , where op is any binary operator among +, -, *, %, /, \, \/, << , or >> (composed assignment lvalue op = expr). The expression op =  y assigns (x op y) to x, and returns the new value of x. The result is not an lvalue; thus

    (x += 2) = 3

is invalid. These assignment operators are right-associative:

    ? x = 'x; x += x *= 2
    %1 = 3*x

* Priority 1 - > (function definition): (vars)- > expr returns a function object, of type t_CLOSURE.

Remark. Use the op = operators as often as possible since they make complex assignments more legible. Compare

  v[i+j-1] = v[i+j-1] + 1    -->    v[i+j-1]++
  
  M[i,i+j] = M[i,i+j] * 2    -->    M[i,i+j] *= 2

Remark about efficiency. the operators ++ and -- are usually a little more efficient than their expended counterpart:

  ? N = 10^7;
  ? i = 0; for(k = 1, N, i=i+1)
  time = 949 ms.
  ? i = 0; for(k = 1, N, i++)
  time = 933 ms.

On the other hand, this is not the case for the op = operators which may even be a little less efficient:

  ? i = 0; for(k = 1, N, i=i+10)
  time = 949 ms.
  ? i = 0; for(k = 1, N, i+=10)
  time = 1,064 ms.