Skip to content

Type system

TML has support for basic and tensor types. Basic types represent scalar values (numbers and booleans) and strings. Tensor types represent multidimensional objects holding values of basic or tensor type.

Basic types

  • Numeric data types:
    • int - Signed integer numbers
    • uint - Unsigned integer numbers
    • real - Real numbers
  • bool - Logical values true and false
  • str - A string of characters
  • char - Single character string

Note

Relation between str and char types is similar to relation between tensor and scalar types, with infinite indexing of char possible. For more details, see section Index Access Broadcasting

Note

Using str and char as variable, parameter, function return or tensor base type in user code is not allowed. str and char are special types reserved for receiving configuration data from model scope.

Literals

Integer

Integers can be defined in a binary, decimal or hexadecimal notation. For better readability, digits may be partitioned by the underscore separator _, which is ignored by the TML compiler.

Example:

a = 0b11    # binary notation
a = 42      # decimal notation
a = 0x2A    # hexadecimal notation


a = 100_000         # same as 100000
a = 100_000.134     # same as 100000.134

Real

Real literals may be given in a x.y or scientific (exponential) notation.

Example:

real a = 2.5
real a = 1.05e-1

Shorthand notation where zeroes are left out (e.g. .3 or 2.) is not allowed.

Tensor types

Tensor types are used to represent arrays, matrices, and general multidimensional objects. Tensor types allow for arbitrary composition of basic and tensor types.

Important

Tensors are statically allocated and their size cannot be changed during the runtime.

Literals

TML supports literals for arrays, matrices and cubes. Row vectors are regarded as a special case of matrices. Literals can be nested to form objects consisting of other tensors, but the limitation is that dimension of one object cannot be larger than 3. For creating objects with dimension higher than 3, other mechanisms should be used.

Following code sample shows basic usage of tensor literals.

# 1D tensor (array)
a = [1, 2, 3]

# 2D tensor (matrix)
b = [1, 2, 3;
     1, 2, 3]

# 3D tensor (cube)
c = [1, 2, 3;
     1, 2, 3 |
     1, 2, 3;
     1, 2, 3]

Following code sample shows usage of nested tensor literals.

arr_of_arrays = [
    [1, 2],
    [1, 2],
    [1, 2]
]

cube_of_arrays = [
    [1], [2];
    [1], [2] |
    [1], [2];
    [1], [2]
]

Following code sample shows usage of arbitrary expressions in tensor literals.

arr = [1, 2, 3]

arr_of_arrays = [
    arr + 1,
    arr + 2,
    arr + 3
]

Type declaration

Type declaration enables explicit assignment of type to a variable that is not dependent on the result of type inference.

Basic type

Variables of a basic type are declared using type keywords in declaration statements.

int a = 1
uint b = 2
real c = 1.5
bool d = true

Tensor type

Variables of tensor types are declared using tensor type constructor in declaration statements. General form of tensor type constructor is tensor<type, len_per_dimension>.

Following example shows usage of tensor type constructor:

# int array of length 3
tensor<int, 3> a = [1, 2, 3]

# int array of length 3, with broadcasting applied
tensor<int, 3> a = 1

# int cube of 2x2x2 elements
tensor<int, 2, 2, 2> a = [
    1, 2;
    3, 4 |
    1, 2;
    3, 4
]

# array of two arrays
tensor<tensor<int, 2>, 2> a = [
    [1, 2],
    [3, 4]
]

Derived type

Sometimes, a variable type depends on a terminal, or a property of the component. Derived type declaration enables declaring a variable of the same type as another one, with type changing according to terminal or property configuration.

Operator <variable>.type is used for derived type declaration. Types used in declaration statements can be derived only from variables from model scope.

Following example shows usages of derived type declaration:

# a is of input terminal's type
t.in.type a = 1

# b is of a's type
a.type b = 2

# c is of in_operand single element's type
t.in_operand[].type c = 3

Type inference

TML is a statically typed language, but with support for type inference. If the variable type is omitted, the TML compiler will statically infer the type based on the initializer expression operands' types.

For basic arithmetic operations, scalar part of type is inferred as the more general type of operand scalar types.

For exponentiation, base scalar type is always real, as. Explicit casting is needed if int type is the desired result type.

Tensor type is inferred per Broadcasting rules.

If both operands are optional, inferred type is optional. If one operand is optional, and the other one is not, inferred type is not optional. Concrete types contained in optional expressions are inferred using ordinary type inference rules.

Example:

a = 10              # type is int
b = (a * 10) / 2    # type is int
a = 5u - 6u         # type is uint and overflow is present
b = 3 ** -2         # special case of type inference, type is real

TML compiler prefers inference to a more general type.

a = true + 1u       # type is uint
b = a + 1           # type is int
c = b * 2.0         # type is real

Type casting

Type casting is necessary for evaluating expression with subexpressions of different types. Type casting can cause data loss when a source value cannot be represented in the target type (i.e. casting a real to an int).

Note

Usage of .type or any of the builtin cast operators results in creating a copy of the argument of the corresponding type. For example, int(arg) results in creating a copy of the arg of type int. Consider this while casting tensors.

Implicit casting

In expressions, implicit type casting is automatically performed when casting from a smaller to a greater type (i.e. casting an int to a real), since no data loss can occur.

Note

We say that the type is smaller if all of its values can be represented in the bigger type.

Warning

Data loss can occur even when converting from int to real depending on the size of the target platform types. E.g., if for the target architecture both integer and real/double are 8 bytes there will be integer values that cannot be represented in the double type.

See warning section on this page.

We should be aware of this when developing code generators for various platforms. For now, we assume that TML int is smaller than TML real type for all current targets.

In assignments, implicit type casting is performed if storing a value to a variable would not cause data loss (i.e. storing an int value in a real variable).

Implicit type casting is related to type inference, as the type of the expression is inferred from operand and subexpression types. When the type of the expression is inferred, operands that are not of the inferred type are implicitly cast to the result type.

Explicit casting

In expressions, explicit type casting is required when casting greater type to a smaller type (i.e. casting a real to an int), since data loss can occur. This makes programmer aware of potentially unwanted effects when writing and reading TML code.

Values can be cast using either type constructors (for both basic and tensor types) and using types derived from other variables. Derived type used in cast expression can be derived from any variable visible at the enclosing scope at that point.

Casting to a basic type

The TML language supports the following operators for casting numeric values:

  • bool(<expr>) - returns the bool representation of <expr>
  • uint(<expr>) - returns the uint value constructed from <expr>
  • int(<expr>) - returns the int value constructed from <expr>
  • real(<expr>) - returns the real value constructed from <expr>
a = bool(123)        # a is 1u
b = bool(-123)       # b is 1u
c = bool(0.0)        # c is 0u

d = uint(true)       # d is 1u
e = uint(false)      # e is 0
f = uint(-123)       # f is 4294967173u because of overflow
g = uint(-123.13)    # g is 4294967173u because of overflow
h = uint(123.13)     # h is 123u

i = int(4294967173)  # i is -123 because of be overflow
j = int(-123.78)     # j is -123
k = int(123.78)      # k is 123

l = real(true)       # l is 1.0
r = real(-12344)     # m is -12344.0

Danger

The following expressions i = int(4294967173) # i is -123 becaue of overflow is unsupported at the moment. The advice is to take care of overflows.

Casting to a tensor type

Casting to a tensor type is performed similarly to casting to a basic type. General form is <tensor_constructor>(<expr>).

a = tensor<int, 3, 3>(5)
b = tensor<tensor<int, 3, 3>, 3, 3>(5)

Casting to a derived type

Operator <variable>.type(<expr>) can be used for converting <expr> to the type of the <variable>. If <variable> is of tensor type, operator <variable><[]>+.type(<expr>) allows declaring a variable of a single tensor element type.

Example:

a = 5
b = a.type(8.2)                 # 8.2 is casted to the type of `a` (`int`)
                                # and assigned to the `b` variable

c = t.in1.type(23)              # 23 is casted to the type of the `in1` terminal array
                                # and assigned to the `c` variable

d = t.in1[].type(25)             # 25 is casted to the type of the terminal array elements

Assignments

Important: All assignments are done by value (implicit mem-copy).

a = [1, 2, 3]
b = a
a[1] = 0
c = b[1] == a[1]        # false

Warning

Assignments that use slicing (e.g. vec[1:5] = 3) are not supported at the moment.