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 directionin) - 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).