Expressions

expression :: list-expression | single-expression
single-expression :: simple-expression
  | self-expression
  | simple-name-expression
  | gvar
  | "(" expression ")"
  | binary-operation
  | boolean-operation
  | unary-operation
  | subscript-expression
  | call-expression
  | array-expression
  | tuple-expression
  | member-reference
  | super-reference
  | anonymous-function
  | cast-expression

Expressions perform all interesting computation in Alore programs. Parentheses can be used for grouping and for overriding operation evaluation order.

Operator precedence

All Alore operators are listed below, from the highest to the lowest precedence, with each operator on the same line having the same precedence:

  1. . () [] (member access, function call, indexing)
  2. **
  3. - (unary)
  4. * / div mod
  5. + - (binary)
  6. to
  7. == != < <= >= > in is
  8. :
  9. not
  10. and
  11. or
  12. as (cast)
  13. , (array constructor)

All operators except the exponentiation operator ** are left associative, i.e. a + b + c == (a + b) + c, whereas ** is right associative, i.e. a**b**c == a**(b**c).

The comma operator is used to form lists of arbitrary length. Associativity rules do not apply to the comma operator.

Lvalues and rvalues

Expressions can be evaluated as either rvalues or lvalues. Lvalues are only expected on the left hand side of an assignment statement. Valid lvalues include:

  1. global variable references
  2. member variable references
  3. local variable references
  4. member references
  5. subscript expressions
  6. superclass member references (but only when referring to a setter)
  7. array constructors (all the array items must be valid lvalue expressions but not array constructors themselves)
  8. uninitialized member constant references (but only in the create method of the class that contains the constant definition)

All expressions are valid in an rvalue context, by contrast. Note that the left hand side of assignment may include rvalue expressions as the subexpressions of lvalue expressions (target of member reference and subscripts in subscript expressions).

The semantics of lvalue expressions are described in section Assignment statement. The descriptions below only apply to rvalue expressions, unless explicitly mentioned otherwise.

Expressions in boolean context

Some expressions are evaluated in a boolean context. Such an expression is expected to evaluate to a boolean value, either True or False, as its value. If the expression evaluates to some other value, a std::TypeError exception will be raised.

Order of evaluation

Operations within a single expression are always evaluated in their natural order as specified by precedence and associativity rules: operations with a higher precedence are evaluated first, and consecutive operations with the same precedence are evaluated left to right (if the operations are left associative) or right to left (otherwise). The natural evaluation order of comma-separated lists is from left to right. Parentheses can be used to change the default operation evaluation order.

Variable and constant references within a single expression may be evaluated in an arbitrary order, but all the operands and arguments of operations must be evaluated before the operation is performed.

Otherwise, expressions and statements are always evaluated in their entirety in the order described in this document, unless some operation causes an exception to be raised. At the instant an exception is raised, any statements or expressions that are not fully evaluated are terminated.

Simple expressions

simple-expression :: int | float | str | "nil"

Integer literals evaluate to Int objects. Correspondingly, float and string literals evaluate to Float and Str objects. The nil expression evaluates to the special nil object. All of these expressions are only valid as rvalues.

Self expression

self-expression :: "self"

The self expression evaluates to the self object, i.e. the object that is bound to the current evaluated method or the object whose getter, setter or initialization expression is being evaluated. The self expression is only valid as an rvalue.

Simple name expressions

simple-name-expression :: id

A simple name expression evaluates to either a local variable, a member of self, or a global definition. Local definitions take precedence over other definitions and member definitions take precedence over global definitions (see Precedence of names for details). Evaluating a local or global reference results in the value of the variable or constant. Evaluating a member reference is equivalent to evaluating the member reference self.id (and it is an error to access member create). It results in the evaluation of the getter for id or in the creation of a bound method object (see Member references for the details).

Referencing global definitions

gvar :: id ("::" id)+ | "::" id

A global reference evaluates to the value of a global definition. The form ::id refers to a global definition in the current module. Otherwise, the last id is the name of a global definition, and all elements before the last :: form the module prefix.

If the module prefix matches the name of a module imported in the current source file, the reference refers to that module. Otherwise, the prefix must form a unique suffix of the name of any module that was imported in the current file. In this case, the reference refers to that module. Otherwise, the reference is invalid and a compile error will be reported.

Binary operations

binary-operation :: expression binary-op expression
binary-op :: "+" | "-" | "*" | "/" | "div" | "mod" | "**" | "==" | "!="
  | "<" | "<=" | ">" | ">=" | "is" | "in" | "to" | ":"

Many of the binary operations are evaluated as method calls. The table below contains the mappings between these operators and methods. The operation will be performed by calling the method mentioned in the table on the left operand with the right operand as the argument (but see (3) in the note section for an exception). The return value of the chosen method is usually the result of the operation (but see (2) in the note section). If the method is not supported by the operand, a std::MemberError exception will be raised.

Operator Method Notes
+ _add
- _sub
* _mul
/ _div
div _idiv
mod _mod
** _pow
== _eq (1)
!= _eq (1) (2)
< _lt (1)
> _gt (1)
<= _gt (1) (2)
>= _lt (1) (2)
in _in (1) (3)

Notes:

(1)  The operation is evaluated in a boolean context, i.e. it must return a boolean value, or a std::TypeError exception will be raised.
(2) The result of the operation is the boolean negation of the value returned by the method.
(3) The _in method of the right operand is called, and the left operand is passed as the argument.

In addition, the following binary operators are not mapped to method calls, but are evaluated directly:

is
Test whether the left operand is a member of a type specified by the right operand. Evaluate to a boolean value. Raise std::TypeError if the right operand is not a type. The nil value is not considered to be a member of any type.
to
Construct a Range object. The left operand is assigned to the start member and the right operand to the stop member. If one of the operands is not an integer, a std::TypeError exception will be raised.
:
Construct a Pair object. The left and right operands are assigned to the left and right members, respectively.
and, or
Perform short-circuit boolean operations (see below).

Equality comparison against a literal nil value is treated specially. In binary operations with == or != as the operator and a literal nil expression as the right operand, the left and right operands are swapped before evaluation.

Boolean operations

boolean-operation :: expression ("and" | "or") expression

The "and" and "or" operators are short-circuit boolean operators. Their arguments are evaluated in a boolean context. The and operator evaluates to True if and only if both of the operands are True; otherwise, it evaluates to False. The or operator evaluates to True if either one or both of the operands is True; otherwise, it evaluates to False.

The boolean operators use short-circuit evaluation. If the first operand of an and operation evaluates to False or if the first operand of an or operation evaluates to True, the second operand is not evaluated at all. In this case, the value of the operation is the same as if the second operand evaluated to any boolean value, i.e. equivalent to the value of the first operand.

Unary operations

unary-operation :: ("-" | "not") expression

Both of the unary operations have unique semantics:

Subscripting

subscript-expression :: expression "[" expression "]"

The subscript expression is evaluated by calling the _get method of the left operand with the right operand (index) as the only argument. The return value of that method is the value of the expression.

The subscript expression is also valid in an lvalue context; see section Assignment statement for details.

Call expressions

call-expression :: expression "(" args ")"
args :: [ "*" expression ]
  | single-expression ("," single-expression)* [ "," "*" expression ]

The call expressions are used for executing the bodies of functions and methods. Additionally the evaluation of most operator expressions falls back to call expressions.

The expression before the left parenthesis evaluates to the called object. The expressions, if any, between the parentheses evaluate to the actual arguments. If the final argument expression is prefixed with *, this argument is a variable-length argument list. Arguments not prefixed with * are the fixed (actual) arguments.

If the called object is a global function, an anonymous function or a bound method, the formal argument list and the body of the function are defined by this object. Otherwise, the formal argument list and the body of the function are those of the _call method of the object being called. If this method is not defined, the object is not callable and an exception will be raised.

If the call expression has only fixed arguments and if all the formal arguments are fixed and required (without default values), the function call is evaluated as follows:

  1. Evaluate the called object expression.
  2. Evaluate the actual argument expressions, from left to right.
  3. If the called object is not callable, raise a std::TypeError exception.
  4. If the number of actual arguments is not compatible with the number of formal arguments, raise a std::ValueError exception. If there are fewer actual arguments than required formal arguments or more actual arguments than fixed formal arguments, the arguments are incompatible. As an exception, if the callee accepts a variable-length argument list, there is no upper bound for the number of arguments.
  5. Create a new invocation of the called object that can hold copies of all the local variables and other state needed by the function call.
  6. Assign the values of the argument expressions to local variables defined by the formal arguments and store them in the invocation. The actual arguments are in the same order as the formal arguments.
  7. Evaluate the body of the function. All local variables in the body refer to this invocation of the function. There may be multiple invocations of any function or method active at a time, and they each keep their own separate state.
  8. If the function exits by a return statement with a return value, this value is the value of the call expressions.
  9. If the function exits by a return statement without a return value or without a return statement after all the statements in the body have been successfully evaluated, the call expression evaluates to nil.
  10. If the function exits because of an uncaught exception, this exception is propagated to the calling function and the expression evaluation will be terminated. The exception will be handled within the calling function as if it was raised within it.
  11. The invocation related to a function call will be destroyed after the function has exited. Individual local variables held by the invocation will outlive the invocation, however, if they may be referred after the invocation exit by an anonymous function object that was created within the invocation.

Caller with a variable-length argument list

If the call expression contains a variable-length argument list expression, it must evaluate to an Array or a Tuple object (or an object of an Array subclass). The items of this sequence behave identically to fixed actual arguments, as if they were included at the end of the argument list as fixed arguments.

Arguments with default values

If some of the formal arguments are optional (i.e. have default argument values), these rules are followed:

  1. If there are more actual arguments than required formal arguments, these actual arguments are assigned to optional formal arguments. These formal arguments behave like required arguments in this invocation, and their default value expressions are ignored.
  2. If there are not enough actual arguments to cover all the optional formal arguments, the rest of the optional arguments are initialized to the values of the initialization expressions. The initialization expressions are evaluated sequentially from left to right. All the available actual arguments are assigned to the corresponding formal arguments before evaluating the initialization expressions.

Callee with a variable-length argument list

If the formals include a variable-length argument list, any number of additional actual arguments are accepted beyond the fixed formal arguments. An Array object is created that holds these extra arguments, in the same order they appear in the argument list. This object is assigned to the formal argument preceded by *. If there are no extra arguments, an empty Array object will be created and assigned instead.

Array constructors

array-expression :: "[" comma-list "]"
comma-list :: single-expression | list-expression
list-expression :: single-expression ("," | ("," single-expression)+ [ "," ])

Construct an Array instance with the values of the subexpressions, separated by commas, as the items. The length of the array is equal to the number of items in the array expression.

Tuple constructors

tuple-expression :: "(" ")" | list-expression

Construct a Tuple instance with the values of the subexpressions, separated by commas, as the items. The length of the tuple is equal to the number of items in the array expression.

An empty tuple is constructed using the () form. To construct a single-item tuple, a trailing comma must be provided. Tuple expressions have to parenthesized when used in contexts where comma has another meaning, such as in argument lists of call expressions.

Member references

member-reference :: expression "." id

This expression refers to the member id of an object, specified by the left operand expression. In an rvalue context, it is evaluated by either constructing a bound method object of type std::Function (if the member refers to a method) or by evaluating the getter method of the member (if the member refers to a member variable or constant). Otherwise, if the member is not defined, raise a std::MemberError exception.

In an lvalue context, id must refer to a member variable (a member with a setter method), or otherwise a std::MemberError exception will be raised. See section Assignment statement for details.

It is an error to access the member create using a member reference.

Bound methods

If the member in a dot expression refers to a method, the result of the operation is a std::Function object that represents the method bound to the specific object. A bound method accepts the arguments specified in the method definition, and when the bound method is called, the body of the method is evaluated so that self refers to the bound object.

Evaluating getter and setter references

Getters and setters behave like methods that take either no arguments (getters) or a single argument (setters). Unlike methods, they can only be directly called; it is not possible to build bound getter or setter method objects. Is is also impossible to call getters or setters using the ordinary () operator; they are only called implicitly when reading a member or assigning to a member.

Superclass member references

super-reference :: "super" "." id

If the member id refers to a method in the current class, this is evaluated to a bound method object representing the definition of the method id in the superclass of the current class. The method must be defined in the superclass. Bound method references are only valid in an rvalue context.

If the member id refers to a member variable or constant in the current class, the getter method of the member defined in the superclass of the current class is called (in an rvalue context) or, in a similar fashion, the superclass setter method is called (in an lvalue context), giving the assigned value as the argument. The getter or setter must be defined in the superclass.

The super construct can only access public members defined in the superclass. All public members inherited by the superclass can be accessed, as well as any members defined or overridden in the superclass.

Anonymous functions

anonymous-function :: "def" "(" arg-list ")" br block "end"

This expression constructs a Function object. The argument list and the body of the function are identical to ordinary function definitions (see section Function definitions). Anonymous functions may refer to local variables defined in the enclosing function or functions. An anonymous function object may outlive the invocation of the enclosing function(s). In that case the local variables defined in the enclosing function(s) that are referenced in an anonymous function will outlive the function invocation.

Cast expressions

cast-expression :: expression "as" cast-type
cast-type :: gvar | "dynamic"

The cast expression has two variants with different semantics:

  1. If cast-type is dynamic, the cast expression evaluates to the value of the operand expression.
  2. Otherwise, the cast type gvar must refer to a type (class or interface) definition. To evaluate the cast expression, the operand expression is first evaluated. If the result is an instance of the cast type, this is also the result of the cast expression. Otherwise, raise std::CastError.