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{int}and{uint}- Special types for unsigned constants that are treated asintoruintdepending on the context
bool- Logical valuestrueandfalsestr- A string of characterschar- Single character string
Following image shows all numeric types and ranges of values that they can represent.
Subtype relationship is based on overlap between value ranges. If set of values in one
range is a subset of values in the other, subtype relation exists. As an exception,
real values cannot represent all integer values and can have a loss of precision, but
TML treats integer types as subtypes of real.

Because int and uint ranges do not overlap, no subtype relation exists.
{int} and {uint} are special types that represent part of the ranges that do
overlap. Special uncommitted integer types allow for simpler usage of literals and
namespace variables in expressions. At the final step of type inference, both types
must become either int or uint. {int} has bias towards becoming int if no
type is enforced (i.e. in implicit declarations), while {uint} has bias towards uint
respectively. If values are mixed, TML has bias towards signed integers types.
Following example shows how typing rules are applied to expressions and declarations:
a = 1 # int
b = 1u # uint
int c = 1u # allowed
uint d = 1 # allowed
e = 1 + 1u # int
int f = 1 + 1u # allowed
uint g = 1 + 1u # allowed
h = a + b # not allowed, types are now committed
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, because calculated value is real
if exponent is negative. Explicit casting is needed if int type is the desired result
type.
TML compiler prefers inference to a more general type.
For scalar types, larger type of the two operands is considered more genaral,
if subtyping relationship between operand types exists (i.e. real is preferred over
integer types, and signed integer are preferred per special bias rule).
Tensor type is inferred per Broadcasting rules. New tensor type can be constructed
based on operand types.
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.
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
TML supports the following operators for casting numeric values:
| Expression | Description |
|---|---|
bool(<expr>) |
Returns the bool representation of <expr>. |
uint(<expr>) |
Returns the uint value constructed from <expr>. If value of <expr> is smaller than 0, 0 is returned. |
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