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 numbersuint- Unsigned integer numbersreal- Real numbers
bool- Logical valuestrueandfalsestr- A string of characterschar- 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 theboolrepresentation of<expr>uint(<expr>)- returns theuintvalue constructed from<expr>int(<expr>)- returns theintvalue constructed from<expr>real(<expr>)- returns therealvalue 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.