Skip to content

Variables

Variables let you give names to values and refer to them later. The name of a variable must be a sequence of letters, digits, and underscores, but it may not begin with a digit. Every variable has an associated type (for more details, see Type system).

Example:

valid_variable_name = 0
a = 0
a2_1 = 0
_a_2 = 0

Declaration

In order to be used, variable needs to be declared. Variables can be declared using explicit or implicit declaration. Every variable belongs to one scope, and is visible in that scope and all child scopes.

Explicit declaration

Explicit declaration is used to declare a new variable in the scope enclosing declaration statement. Expression in declaration statement and later uses of variable must respect declared type.

General form of explicit declaration:

<type> <name> = <expr>

Examples:

real a = 1
real b = 2.0
bool c = false
int d = 5.0    # TypeError: Expected int or type convertible to int, but received real

Implicit declaration

Implicit declaration can be used as a shorter alternative to explicit declaration. Variable type is inferred from expression operands. Later assignments must respect type inferred on implicit declaration.

In global scope, every assignment statement is treated as implicit declaration. In local scope, assignment statement is treated as implicit declaration if variable with specified name does not exist.

General form of implicit declaration:

<name> = <expr>

Examples:

a = 5.0     # real
b = false   # bool
c = 1 + 1.5 # real

#later uses
c = "test"  # TypeError: Expected real or type convertible to real, but got string

Scopes

Variable names live in scopes. Scope represents a range of TML code where a variable name exists and refers to the same value. TML uses lexical scoping. TML divides scopes into global scope and local scope(s).

Global component scope contains variables defined at the top-most outer level. In addition, each statement that contains nested code block has its local scope. Every function has at least one local scope, defined by the function body block. Functions can also have additional child local scopes, if they contain nested block statements such as if and for.

int a = 0           # global scope

fn init():

   int b = 5        # function local scope

   for i = 1:10:
       int c = 3    # for loop local scope
   end

end

Global scope

Global scope is the parent scope of all local scopes. Variables declared in global scope can be accessed in all local scopes.

Redeclaration of variables is not allowed in global scope. Forward referencing of variables is not allowed. Forward referencing of functions is allowed.

Variables in global scope can be declared either using explicit or implicit declaration. Every occurrence of variable assignment is considered as implicit declaration.

Functions that can be called in global scope are: - built-in functions (see Built-ins) - custom functions that are pure (i.e. do not depend on nor change global state) - custom functions that are guaranteed to terminate (i.e. do not contain while)

int a = 1 # explicit declaration
b = 2.0   # implicit declaration

a = 3     # Error: redeclaration of 'a'

x = y     # Error: 'y' is undeclared
y = 5

Local scope

Explicit declaration creates a new variable in the scope enclosing the declaration statement. Assignment statement is treated as implicit declaration if a variable with the specified name does not exist. Assigning to a variable that exists in the outer scope will reference the existing variable instead.

To create a new variable with the same name in the current scope, variable must be declared using explicit declaration.

int a = 5

fn init():

   a = 10    # This references global 'a'

   # If we want variable by the same name we have to use declaration syntax
   int a = 10       # This variable is a new variable at the function level
   if true:
      a = 5         # This variable references function-level `a`
   else:
      int a = 3     # This is a new variable inside the `else` branch
   end

end

Namespaces

TML has a set of special variables which can be accessed using t, p and n namespace. Variables from namespace can be accessed using the dot notation (for example t.in1).

Variables in t, p and n namespaces are predefined and cannot be redefined. Variables from the t namespace can be used to access variables generated from components terminals, the p namespace variable is similarly used to access component's property values, whereas the n namespace variable is used to access the values from the component's namespace. Example:

Assuming a component has:

  • three terminals: out (direction out), and in1 and in2 (both with direction in)
  • a property named mul_factor, and
  • a namespace variable named div_factor.

we can write:

t.out = ((t.in1 + t.in2) * p.mul_factor) / n.div_factor

Variables from built-in namespaces cannot be redeclared by the user.

Variables generated from the input terminals and properties cannot be written to (i.e. cannot be lvalues).

Variables generated from the output terminals must be initialized inside output_fnc before the first reference on the right-hand side of the statement.

Example:

fn output_fnc():
    a = t.out              # ValueError - Cannot use out terminal before initialization.
    t.out = t.in1          # valid - This statement is considered as the proper initialization
                           #         of the output terminal that can be safely used afterward.
    t.in1 = 5              # TypeError, input terminals are not lvalue
    p.mul_factor = 5       # TypeError
    n.div_factor = 5       # TypeError
    t.out += t.in1         # valid - The output terminal is properly initialized and can be used.
                           #         inside in-place assignment.
    t.out = t.out + t.in1  # valid - The output terminal is properly initialized and can be used
                           #         as rvalue.
end

Using terminal variables outside of the output_fnc is considered as an error.

fn init_fnc():
    t.out = t.in    # ValueError - Cannot use terminal variables outside
                    #              `output_fnc`
end

Note, one can use the terminal variables inside functions called from the output_fnc.

fn output_fnc():
    setup_output_terminal()
end

fn setup_output_terminal():
    t.out = 1  # The enclosing function is called from the `output_fnc`
               # so the output terminal can be used.
end

However, if a function manipulating with an output terminal variable is called from output_fnc and another function not called from the output_fnc, then the TML compiler raises an error. See the following example for more information.

fn output_fnc():
    setup_output_terminal()
end

fn some_other_fnc():
    setup_output_terminal()  # TML reports an error because the
                             # function changing output terminal is called
                             # from the function different that `output_fnc`.
end

fn setup_output_terminal():
    t.out = 1
end

Note

If not stated differently, the namespace refers to the TML namespace (t, p, or n, respectively).