Expressions

Expressions

An expression is any executable construct, which is everything in the grammar except:

  • a function or service definition
  • a typedef or resource declaration
  • the null statemment, ";"

These are termed non-executables. The function, service, typedef and resource constructs have the effect of creating their entity in the Inq environment where the script was parsed. The null statement has no effect at all.

An expression is evaluated to yield a value and to cause any effect it may have, such as creating new nodes or altering existing values.

Constants and Variables

An expression evaluates to a constant value if it is either:

  • a value type constant;
  • a node that is an alias to a value type constant;
  • a node that is an alias to a variable where mutation via this alias would compromise the environment, known as field ripping and discussed in the section Transactions.

An alias to a value type constant is created by an anonymous declaration to a literal value:

any consts.nominalTemp = 25;

or to a function whose return value is such:

any constValue = call returnsConstValue();

A variable is changed by one of the mutating operators

  • assignment
  • pre/post increment/decrement

and the setnull() function. Mutation can only occur through a path reference, that is an expression of the form

[<prefix>]node1.node2.node3

where the optional prefix denotes one of the specific node spaces.

Parse-time Assignment Errors

Although the orthogonal nature of the Inq language suggests that it should be possible, assignment to an arbitrary expression is an error. For example, all functions have a return value. The script below is semantically equivalent to the statement $this.container.currentTemp = 27;

local function currentTemp()
{
  $this.container.currentTemp;
}

.
.

call currentTemp() = 27;   // parse-time error

Run-time Assignment Errors

The Inq run-time intervenes in the evaluation of a mutation to perform more than just its effect when the containing map

  • is a managed instance in the server environment
  • contains one or more variables that have been bound to a GUI view with renderinfo in the client environment.

In these cases Inq respectively enters the instance into the current transaction and raises a model event to refresh the GUI view. To correct the parse error in the above example we can rewrite the assignment as follows:

any v = call currentTemp();
v = 27;  // run-time error

However, if $this.container is being managed by Inq then obtaining another reference to the value is an occurrence of field ripping and performing mutation via this reference generates a run-time error. Field ripping can occur in either of the following situations

Though not, in itself, illegal, field ripping makes a constant out of the underlying variable. The value will be seen via the alias, but mutations through it would circumvent the run-time management and so are prevented. Aliases through unmanaged containers are not subject to any checks and do not generate mutation exceptions.

Operators

Individual expression terms are combined with operators to create expression groups. The following table lists the operators in order of their evaluation precedence:

Operator Precedence, Highest to Lowest
Operator Description
<expression>
()
++
--
A built-in function or construct
Grouping parentheses
Post-increment
Post-decrement
-
!
++
--
Unary Minus
Logical Not
Pre-increment
Pre-decrement
^^ Exponentiation
*
/
%
Multiplication
Division
Remainder
+
-
Binary addition
and subtraction
< <=
> >=
~~
Less than / Less than or equal to
Greater than / Greater than or equal to
Regular Expression match
==
!=
Equal to
Not equal to
&& Logical AND
|| Logical OR
? : Ternary
=
+= -=
*= /= %=
&&= ||=
Assignment
Addition/subtraction assignment
Multiplication/division/remainder assignment
Logical AND/OR assignment

With the exception of assignment, all operators associate left-to-right, that is evaluation of an expression occurs in this direction within a precedence group. Assignment associates right-to-left.

Order of Evaluation

All terms of an expression are evaluated unless the they are operands to the operators && (logical AND), || (logical OR) or ?: (ternary).

Mathematical Binary Operators

A mathematical binary operator is processed in the following stages:

  1. the left hand operand is evaluated;
  2. the right hand operand is evaluated;
  3. if either operand is unresolved null an exception is thrown;
  4. if either operand is the null value the result of the operation is the null constant;
  5. if the operands are of different types, the operand of lower rank is converted to that of the higher;
  6. the operation is performed.

The result of the operation is a value of the same (or highest) rank, except for the relational operators, which return a boolean.

Conditional Operators

A conditional binary operator is processed in the following stages:

  1. the left hand operand is evaluated and converted to a boolean value if required;
  2. if the operator is && and the left operand evaluates to false the operation returns false;
  3. if the operator is || and the left operand evaluates to true the operation returns true;
  4. the right hand operand is evaluated and converted to a boolean value if required;
  5. if an operand is unresolved null it is equivalent to false;
  6. if either operand is the null value the result of the operation is false;
  7. the operation is performed.

The ternary operator evaluates its condition. If the result is true the second operand is evaluated and its result returned. If it is false the third operand is evaluated. The second and third operands do not have to return results of the same type.

Other Operators

A number of other operations are defined that, by their appearance, might have been deemed built-in functions. They are operators because, unlike functions

  • multiple operands are evaluated left-to-right;
  • numeric operands can be of any type.
Other Operators
Operator Description Return Type
sqrt(x) Returns the correctly rounded positive square root of the argument double
sin(x) Returns the trigonometric sine of an angle. The argument is in radians double
cos(x) Returns the trigonometric cosine of an angle. The argument is in radians double
tan(x) Returns the trigonometric tangent of an angle. The argument is in radians double
asin(x) Returns the arc sine of an angle, in the range of -pi/2 through pi/2 double
acos(x) Returns the arc cosine of an angle, in the range of 0.0 through pi double
atan(x) Returns the arc tangent of an angle, in the range of -pi/2 through pi/2 double
ceil(x) Returns the smallest (closest to negative infinity) double value that is not less than the argument and is equal to a mathematical integer double
floor(x) Returns the largest (closest to positive infinity) double value that is not greater than the argument and is equal to a mathematical integert double
todegrees(x) Converts an angle measured in radians to an approximately equivalent angle measured in degrees double
toradians(x) Converts an angle measured in degrees to an approximately equivalent angle measured in radians double
exp(x) Returns Euler's number e raised to the power of the argument double
logn(x) Returns the natural logarithm (base e) of the argument double
round(x) Returns the closest integer to the argument int if the argument is of float or lower rank, long otherwise
max(x, y) Returns the argument x or y, whichever has the greater value One of the two arguments is returned, regardless of whether either was converted to a higher rank to make the comparison

Function Calls

Function calls can be divided into two categories

  • calls to scripted functions, defined with the function construct;
  • calls to built-in functions.

In either case, Inq does not define the order of evaluation of the function arguments, so applications cannot rely on side-effects that make one argument dependent on another.

Scripted Functions

Functions are described in Modules, Functions and Services. Each argument in a call statement is evaluated in no particular order. Argument passing then proceeds as follows:

  1. If the argument evaluates to unresolved null the corresponding argument is unavailable in the called function, that is references to it will result in unresolved null.
  2. The function definition determines whether the argument is passed by value or by reference. If passed by value the argument is copied to a value of the type of the defined argument. No ranking alignment takes place, so the argument value must be compatible with the defined type. If the argument evaluates to the null constant (or is the null value for the type), the defined argument will be the null value for its type.
  3. If passed by reference, the value itself is available in the called function and mutations, subject to exceptions because of field ripping, will be visible in it after the call returns.

Built-in Functions

The order of argument evaluation and even whether an argument will be evaluated is not defined. In some cases, a built-in function requires an argument to actually be (rather than just convertible to) a particular value or container type.

Arguments to built-in functions are always passed by reference. Unless explicitly stated in a function's synopsis, arguments are not mutated.

Definition of Equality

Equality Between Value Types

The equality operator performs ranking alignment of its operands, so two values can be equal regardless of whether they are the same type. The following example compares values of the double and int types:

sqrt(225) == 15;
true

Fixed Precision Decimals

A fixed precision decimal value can only be compared equal to another fixed precision decimal and values with differing precision will always be not equal:

decimal:2 d2 = "1.23";
1.23
decimal:3 d3 = "1.230";
1.230
d2 == d3;
false

Strings

Two string values are equal if they represent the same sequence of characters as each other.

Null values and The null Constant

If two values are the null value for their type then they are equal, whether or not they are of the same type.

A value that is null is equal to the null constant:

string s = null;

int i;
0
setnull(i);

i == s;   // i and s are both null
true

i == null;
true

isnull(s);
true

Equality Of Container Types

In general, containers are considered equal if they are of the same generic type (map, array or set) and contain the same number of children whose values are equal. The equality operator is applied to the container operands and recursively to any children that are, themselves, containers, to the deepest level.

For maps, whose children are keyed, there is the additional requirement that the key set in the two operands must also be equal.

Arrays are always ordered, so two arrays are only equal if their children are equal and occur in the same order. Though the hash-based containers can support ordering, this is not considered when evaluating equality.

If the children being compared at any point during the evaluation are value types then no ranking alignment takes place. Consider the following:

array a1 = (2, 3);    // both values of type int
[2, 3]
array a2 = (2, 3l);   // one int, one long
[2, 3]
a1 == a2;
false

Here is another example that shows different map types compare equal and independently of any ordering

omap om1;         // Explicitly declare an ordered map
{} []
any om1.a = 4;    // Put in an integer and a float as children "a" and "b"
4
any om1.b=2.0f;
2.0
any sm1.a=4;      // The smap in implicitly created by these declarations
4
any sm1.b=2.0f;
2.0

// Just display the two maps. The omap shows the ordering
// of the children, which is as they were inserted
sm1;
{a=4, b=2.0}
om1;
{a=4, b=2.0} [a, b]

// Compare them
om1 == sm1;
true

// Sort the ordered map. The maps are still equal
sort(om1, $this);
{a=4, b=2.0} [b, a]
om1 == sm1;
true

// Of course, equality is commutative
sm1 == om1;
true

Parser Execution

Irrespective of its mode of operation, the parser executes statements as they are parsed. The non-executables of function and service definitions are placed into their respective name spaces for the package of the defining parse module.