Skip to content

Expressions

Arithmetic operators

Arithmetic operations can be applied on numeric scalars and tensors. Broadcasting is applied if needed per arithmetic broadcasting rules. See the Broadcasting section for more information about when broadcast is applicable. All operators in TML behave as element-wise operators.

Warning

At the moment, TML does not support the following:

  • Operators: \ (left division)

TML currently supports the following in-place assignments on scalars and vectors:

  • +=
  • -=
  • *=
  • /=
  • %=

The assignments using the operator %= can be applied on scalars only.

Examples of using arithmetic expression.

m1 = [1, 2; 3, 4]
m2 = [5, 6; 7, 8]

#
# Addition
#
x = m1 + m2
#> [6, 8; 10, 12]

#
# Subtraction
#
x = m1 - m2
#> [-4, -4; -4, -4]

#
# Element-wise multiplication
#
x = m1 * m2
#> [5, 12; 21, 32]

#
# Element-wise right division
#
x = m1 / m2
#> [0.2, 0.33; 0.4286, 0.5]

#
# Element-wise left division
#
x = m1 \ m2
#> [5.00, 3.00; 2.33, 2.00]

#
# Power
#

# scalar
a = 2
b = 5
x = a ** b
#> 32

# matrix and scalar
x = m1 ** 2
#> [7, 10; 15, 22]

# matrix and matrix
x = m1 ** m2
#> [1, 64; 2187, 65536]

#
# Negation
#
x = -m1
#> [-1, -2; -3, -4]

# Transpose
x = m1'
#> [1, 3; 2, 4]

Dot product

Dot product operation is supported for scalars (T), vectors (tensor<T, x>) and matrices (tensor<T, x, y>). Dot product is not supported for nested tensors. Dot product returns either a scalar or a tensor as the result. Scalar part of the result type is inferred from operand base scalar types, like in ordinary binary operations. Full result type depends on the combination of operand types on which the operation is applied.

Operation a .* b is defined by the following rules:

  1. If both a and b are vectors, the result is inner product of vectors
  2. Vector dot product is a commutative operation
  3. Both vectors must be of the same length
  4. Result is sum of products of corresponding vector elements (i.e. a1 * b1 + ... + an * bn)
  5. Result is a scalar
  6. If both a and b are matrices, the result is matrix multiplication
  7. Matrix multiplication is not a commutative operation
  8. Inner dimensions of matrices must be equal (i.e. matrix multiplication is defined only for matrices of dimensions A x B and B x C)
  9. Result type is a matrix of dimensions A x C
  10. If one operand is a scalar, and the other is either a vector or a matrix, the result is element-wise multiplication by the scalar value
  11. This is not equivalent to element-wise multiplication of scalars and tensors, because nested tensors and tensors of arbitrary dimensions are forbidden
  12. If a is a vector and b is a matrix, the result is sum product of vector and matrix elements per column
  13. Length of vector must be equal to the number of matrix rows
  14. If a is of length A, b must be of dimensions A x B
  15. If a is a matrix and b is a vector, the result is sum product of vector and matrix elements per row
  16. Length of vector must be equal to the number of matrix columns
  17. If b is of dimensions A x B, a must be of length B

Following examples show usage od dot product:

a = 2
b = [1, 2, 3]
c = [
    1, 2, 3;
    4, 5, 6;
    7, 8, 9
]
d = [
    9, 8, 7;
    6, 5, 4
]

r1 = a .* a # r1 = 4
r2 = a .* b # r2 = [2, 4, 6]
r3 = a .* c # r3 = [
            #    2, 4, 6;
            #    8, 10, 12;
            #    14, 16, 18
            # ]

r4 = b .* c # r4 = [30, 36, 42]
r5 = c .* b # r5 = [14, 32, 50]
r6 = d .* b # r6 = [46, 28]

r7 = d .* c # r7 = [
            #    90, 114, 138;
            #    54, 69, 84
            # ]

Note

Rules 4. and 5. present simplified and specialized versions of Numpy rules 4. and 5. present in numpy.dot documentation. We found Numpy rules confusing for users and decided to implement dot product which covers currently needed use cases.

Range Expressions

Ranges present a special built-in syntax for describing bounded and unbounded sets of values, with optional step for spacing. If step is omitted, elements are spaced by 1. Ranges can be only of int type.

Ranges can be used in for loops and in tensor indexing. Support for using ranges for initializing arrays will be added in the future.

TML supports following range types:

Name Syntax Example Range Note
Range from to <expr>:<expr> 1:10 [1, 10) Can be used in for loop and indices
Range from <expr>: 1: [1, max) Can be used only as an index for slicing
Range to :<expr> :10 [0, 10) Can be used only as an index for slicing
Range from step to <expr>:<expr>:<expr> 1:10:2 [1, 10), spaced by 2 Can be used in for loop and indices
Range all : a[1, :, :] Can be used only as an index for slicing

Following example shows range expression usage in for loop:

for i = 1:10:
    x = i + 1
end

for i = 1:10:2:
    x = i + 1
end

Index Expression

Indexing expression allows referencing elements of a tensor. Number of indices must be equal to the number of tensor dimensions. Index expressions can be applied recursively to obtain elements of nested tensors. Index expressions can be nested arbitrarily.

When a scalar value is used as index, dimension is removed from an object (i.e. resulting objects present a hyperplane extracted from n-D space). When a range expression is used as index, dimension is retained while the length of the dimension is adjusted.

If a tensor has more than one dimension, indexing it using only one index as a plain array of values is possible. Indexing a multidimensional tensor is possible only using either the same number of indices or using one index. If single index is used, tensors are represented as an array holding elements from the left to the right (e.g. [1, 2, 3; 4, 5, 6] can be viewed as [1, 2, 3, 4, 5, 6]).

Note

Index expression can be ignored by the compiler if a scalar is indexed. As ignoring expressions with side effects would alter program execution semantics, having side effects in index expressions is not allowed.

Using such code would not be useful in practical terms, and enforcing such restrictions is necessary for avoiding subtle bugs and having predictable behavior.

Following example shows usage of index expression for basic indexing:

array = [1, 2, 3]

a = array[0]            # a = 1

matrix = [
    1, 2, 3;
    4, 5, 6;
    7, 8, 9
]

b = matrix[0, 0]        # b = 1
b = matrix[5]           # b = 6

nested_array = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

c = nested_array[0]     # c = [1, 2, 3]
d = nested_array[0][0]  # d = 1

Note

Index expression results in a slice of the original supported tensor. Slice is just a view to the supported vector allocated by the TML compiler.

Note

Octave-like append, insert and delete operations are not supported. Arrays are static by nature.

Warning

Vectors cannot be used for indexing like in Octave (for now).

Indexing Terminal Groups

TML supports simple indexing terminals and groups in the following way:

a = t.in[0]           # a is equal to the first input terminal

Indexing strings

Strings can be indexed in a similar way to how tensors are indexed. Indexing str type returns char. Char is treated like a scalar and infinite indexing is allowed (and ignored).

# x == "asd"
# x.type == str
# x[].type == char
# x[][][].type == char

if x == "asd":
    a = 0
end

if x[0] == "a":
    a = 0
end

if x[0][1][2] == "a":
    a = 0
end

# y == "a"
# y.type == char
# y[].type == char
# y[][][].type == char

if y == "a":
    b = 0
end

# indexing is ignored!
if y[0] == "a":
    b = 0
end

# indexing is ignored!
if y[0][1][2] == "a":
    b = 0
end
end

Relational operators

Operator Usage Description
> x > y true if x is greater than y
>= x >= y true if x is greater than or equal toy
== x == y true if x is equal toy
< x < y true if x is less than y
<= x <= y true if x is less than or equal to y
!= x != y true if x is not equal to y

Relational operations can be applied on boolean scalars and tensors. Broadcasting is applied if needed per logical and relational broadcasting rules. See the Broadcasting section for more information about when broadcast is applicable. Relational operators behave as element-wise operators.

# scalar
m1 = [1, 2; 3, 4]
x = m1 > 1
#> [0, 1; 1, 1]

# matrices
m2 = [5, 3; 1, 5]
x = m2 > m1
#> [1, 1; 0, 1]

m3 = [1, 1; 4, 4]
x = m1 == m3
#> [1, 0; 0, 1]

# vector
v1 = [1, 2]
x = m1 > v1
#> [0, 0; 1, 1]

If an expression results in a boolean tensor, using all or any is necessary in selection statements.

fn foo() int:
    a = [1, 2, 3]
    b = [-3, -4, 5]
    c = [-1, 2, 0]

    if all(a > b):
        # Not all elements of `a` are greater than elements of `b`.
        # Thus this branch is skipped.
        res = 1
    elseif any(a > c):
        # At least one element of `a` is greater than the corresponding
        # element of `c`. Thus this branch is executed.
        res = 2
    else:
        res = 3
    end

    return res  # res is 2
end

Logical operators

Logical operations can be applied on boolean scalars and tensors. Broadcasting is applied if needed per logical, relational and bitwise broadcasting rules. See the Broadcasting section for more information about when broadcast is applicable. Logical operators in TML behave as element-wise operators.

TML supports the following logic operators that behaves as element-wise operators assuming implicit broadcasting of smaller operands.

Operator Usage Description
and x and y Applies logical-and on corresponding pair of elements of x and y
or x or y Applies logical-or on corresponding pair of elements of x and y
not not x Applies logical-not on elements of x

The following examples shows how logic operators behave on scalars:

a = True
b = False

c = a and b # c = 0
c = a or b  # c = 1
c = not a   # c = 0

The following examples shows how logic operators behave on tensors:

a = [True, False, True]
b = [False, True, False]

c = a and b # c = [0, 0, 0]
c = a or b  # c = [1, 1, 1]
c = not a   # c = [0, 1, 0]

Bitwise operators

Bitwise operations can be applied on uint scalars and tensors. Broadcasting is applied if needed per logical, relational and bitwise broadcasting rules. See the Broadcasting section for more information about when broadcast is applicable. Bitwise operators in TML behave as element-wise operators.

TML supports the following logic operators that behaves as element-wise operators assuming implicit broadcasting of smaller operands.

Operator Usage Description
<< x << y Applies bitwise left shift of x bits by y count
>> x >> y Applies bitwise right shift of x bits by y count
^ x ^ y Applies bitwise exclusive or (XOR) on bits of corresponding pair of elements of x and y
& x & y Applies bitwise-and on bits of corresponding pair of elements of x and y
\| x \| y Applies bitwise-or on bits of corresponding pair of elements of x and y
~ ~x Applies bitwise bit flip of bits on elements of x

The following examples shows basic bitwise operator usage:

uint a = 0u
uint b = 1u

c1 = a << b
c2 = a >> b
c3 = a ^ b
c4 = a & b
c5 = a | b
c6 = ~a

Neutral Elements

TML allows treating inline guarded optionally present variables as neutral elements in binary arithmetic, logical and bitwise expressions. An expression is treated as neutral element if all subexpressions are optional and missing. Relational and all unary expressions do not have neutral element. The following table shows left and right neutral elements of binary arithmetic operations.

Operator left neutral right neutral
.* not supported not supported
** not supported 1
/ not supported 1
* 1 1
- not supported 0
+ 0 0
and true true
or false false
^ all bits 0 all bits 0
& all bits 1 all bits 1
\| all bits 0 all bits 0
<< not supported 0
>> not supported 0

Note

Optional expressions and neutral elements are not implemented as binary operations in the compiler. They are implemented as pruning missing expressions from code.

Operator Precedence

The following table contains arithmetic expression that TML supports, with their respective priority and associativity. Higher number represents higher priority.

Priority Operator Note Associativity
14 .<attr> Attribute access (e.g. a.len) left
14 [] Indexing operator (e.g. a[1], a[1][2]) left
14 () Parenthesis (e.g. (1 + 2) * 3) left
13 ' Transpose (e.g. a') left
13 ** Exponentiation (e.g. 2 ** 4) left
12 +, - Unary operators (e.g. +a, -a) right
12 ~ Bitwise unary NOT (e.g. ~a) right
11 *, /, %, \\, .* Binary operators (e.g. a * b) left
10 +, - Binary operators (e.g. a + b) left
9 <<, >> Binary bitwise shift operators (e.g. a >> b) left
8 ?: Elvis operator (e.g. a ?: 0, (a? + b?) ?: 0) left
7 <, <=, >, >=, ==, != Comparison operators (e.g. a == b) left
6 & Bitwise binary AND (e.g. a & b) left
5 ^ Bitwise binary XOR (e.g. a ^ b) left
4 \| Bitwise binary OR (e.g. a \| b) left
3 not Logical unary NOT (e.g. not a) right
2 and Logical binary AND (e.g. a and b) left
1 or Logical binary OR (e.g. a or c) left

Note

Priority and associativity represent properties defined in the grammar.

For binary operations that are mathematically both left and right associative (e.g. addition, multiplication), associativity represents the shape of AST after parsing.

Expression evaluation represents a depth first left-to-right traversal of AST, so associativity defined in the grammar alters the evaluation order.