Syntax fundamentals
This document describes the basics of the Toit language. To learn about Toit's documentation convention, see the language documentation convention section.
Whitespace and indentation
Toit uses whitespace and indentation to construct the code structure: comment. For example:
// Defines globals first, and then uses main function to // print out something. i := 1 j := 10 main: while i < j: print i i = i + 1
Declaration and assignment
In the above, we declared new variables i
and j
using
colon-equals. We assigned a new value to the existing variable,
i
, using a single equals sign. To declare a variable without
giving it a value, you can use a question mark: := ?
.
The compiler will check that the new variable is assigned a value before it is used.
Comments
In Toit, a single line comment begins with a double slash //
followed by the comment. For example:
For multi-line comments, use */
.
Identifiers and keywords
Identifiers are names that identify globals, locals, functions, classes, libraries, and other objects in Toit.
- Identifier names can contain only letters, numbers, dashes (-) and underscores (_), but cannot contain
spaces. Dashes must be enclosed by letters or numbers. Dashes are equated with underscores. That is,
foo-bar
andfoo_bar
refere to the same entity. When possible, prefer dashes. - Identifiers need to start with a letter or an underscore (_), but not with a number. The following characters can be alphanumeric or underscore.
- Toit identifiers are case-sensitive.
- Identifier names cannot be the same as keywords and built-in functions in Toit.
The following Toit keywords cannot be used for naming identifiers:
as abstract assert break class continue else false finally for if import export null return static true try while or and not
In addition to these keywords, there are some "pseudo keywords" - like
constructor
- that cannot be used in certain contexts. Trying these
pseudo-keywords as globals name will generally give an error message in the
IDE.
Another example of pseudo-keyword is operator
that is allowed as a local but
cannot be the name of a function. Similarly, string
can be used like a normal
global but would shadow the string
type.
String and character literals
Single quotes for are used for character literals and are equivalent to integer literals of the corresponding Unicode code point, or rune.
print 'A' // Prints "65", the ASCII code of 'A'. print '*' // Prints "42", the ASCII code of '*'. print 'æ' // Prints "230", the Unicode code point of 'æ' print '€' // Prints "8364", the Unicode code point of the Euro symbol. a /int := 'a' // a now contains the integer 97.
Toit uses only double quotes "
to denote a string literal:
If a string contains a double quote, a backslash, or a dollar sign,
you must use the backslash \
to
escape it. The backslash
can also be used for characters that are difficult to
type
For example:
The unescaped operator $
is used for string
interpolation.
The methods available on string objects are documented in the string class. See also the guide to Toit strings.
Toit globals and constants
The :=
operator introduces a new global variable with the name of the given identifier (here "global-name").
If the global is final (that is, can't be assigned to), then you can use ::=
as in:
Note that this in this case, global-number
is actually a constant, and should thus be written in capital characters:
A final global that isn't a constant could be something that is mutable itself, such as
In this case the global is initialized with an empty list. The content of the list can change, but it will always be the same list.
When you name a global, you should adhere to rules defined earlier about identifiers.
Global variables (and static class
variables) are initialized when
they are first used. This is noticeable if their value is
determined by some function call, for example Time.now
TIME ::= Time.now // Time.now is called on first use of TIME. main: print "time: $Time.now" // Print time at program start. sleep --ms=2000 // Sleep for two seconds. print "TIME: $TIME" // Print the global constant. sleep --ms=100 print "TIME: $TIME" // The global constant remains unchanged.
This program will print two different times, the second one twice.
Indexing syntax
Indexing into lists, maps, byte arrays, and strings can be
done with square brackets, []
. The first element has an
index of zero.
For example, an ASCII string is a sequence of characters, and you can access its elements using square brackets [] and indexes.
str := "Toit String" byte-array := #[1, 42, 103] list := [1, 10, 100, 1000] map := { "foo": 42, "bar": 103, } main: print str[0] // Prints "84", the ASCII code of 'T'. print byte-array[1] // Prints "42", the byte at position 1. print list[3] // Prints "1000", the integer at position 3. print map["foo"] // Prints "42", the value with the key "foo".
Indexing into strings is done by UTF-8 byte offset, so the story is a little more complex for non-ASCII strings.
Toit also has support for slices. These are views into part of a list, byte array, or string:
print str[0..4] // Prints "Toit", the first 4 bytes of "Toit String". print str[..4] // Prints "Toit", the first 4 bytes of "Toit String". print str[5..] // Prints "String", the last part of "Toit String".
See also the description of slice syntax in the string guide.
A slice may keep the underlying object alive, preventing a
garbage collection. To avoid that you can use copy
to
create a new object containing only the elements in the
slice:
long-lived-global/ByteArray := #[] main: huge := ByteArray 1000000 // The slice keeps huge alive: long-lived-global = huge[1000..1050] // Makes a copy, so huge can be garbage collected: long-lived-global = long-lived-global.copy
Continuation of statements
Toit uses a newline character to separate statements. It places each statement on one line. However you can break very long statements into multiple lines.
Line breaks in function arguments
In a long list of arguments for a function call you can use a new line for each argument.
A long statement can also span multiple lines by using the backslash () character.
The following example illustrates how to use per-argument newlines, or the backslash () character to continue a statement in the second line:
// A method with many parameters. foo a b c d e f g h i j: ... main: some-long-local1 := 1 some-long-local2 := 2 some-long-local3 := 3 some-long-local4 := 4 some-long-local5 := 5 some-long-local6 := 6 some-long-local7 := 7 some-long-local8 := 8 some-long-local9 := 9 some-long-local10 := 10 // Once there is a newline, all further arguments must be // on their own line: foo some-long-local1 some-long-local2 some-long-local3 some-long-local4 some-long-local5 some-long-local6 some-long-local7 some-long-local8 some-long-local9 some-long-local10 // We can also write all of them in one line: foo some-long-local1 some-long-local2 some-long-local3 some-long-local4 some-long-local5 some-long-local6 some-long-local7 some-long-local8 some-long-local9 some-long-local10 // But that becomes hard to read fast. // If we want to cut the line at some point we need a `\` to conceptually continue the first line: foo some-long-local1 some-long-local2 some-long-local3 \ some-long-local4 some-long-local5 some-long-local6 \ some-long-local7 some-long-local8 some-long-local9 \ some-long-local10
Putting a line break between function arguments also has the effect of grouping the arguments, which can make parentheses unneccessary:
two-arg-function a b: return a + b three-arg-function a b c: return a + b + c my-func a/int b/int c/int -> int: // Long line with many parentheses. x := three-arg-function (two-arg-function a b) (two-arg-function b c) (two-arg-function c a) // One line per argument looks better: y := three-arg-function (two-arg-function a b) (two-arg-function b c) (two-arg-function c a) // For a cleaner look we don't need parentheses around // the arguments that have their own line. z := three-arg-function two-arg-function a b two-arg-function b c two-arg-function c a return x + y + z
Line breaks in operator expressions
If you need to break up a large expression, the second and subsequent lines should be indented more than the first:
long-expr-function long-argument-name other-argument-name: return long-argument-name + 2 * other-argument-name - long-argument-name.abs
As seen here, the preferred style is to put binary operators after the line break, not before.
Line breaks in multi-line or very long string literals
See the guide to strings on how to format multi-line strings and how to line-break very long string literals.
Precedence
Operators and other syntactic elements have precedence, like in other languages. From highest to lowest:
Postfix operators
The postfix operators, ++
and --
have the highest precedence. Thus
the following two lines are equivalent:
Toit does not have the prefix forms of ++
and --
:
Math operators
The math operators have the next highest precedence. In order of descending precedence the order is:
- Unary operators,
-
~
- Multiplicative operators,
*
/
%
- Additive operators,
+
-
- Bit-shift operators,
<<
>>>
>>
- Bit-and operator,
&
- Bit-xor operator,
^
- Bit-or operator,
|
Thus the following pairs of lines are equivalent:
a1 := ~x * 3 a2 := (~x) * 3 a3 := 4 + 2 * 3 a4 := 4 + (2 * 3) a5 := x >> y + 1 a6 := x >> (y + 1) a7 := x & mask << distance a8 := x & (mask << distance) b1 := x ^ y & z b2 := x ^ (y & z) b3 := x | y ^ z b4 := x | (y ^ z)
The math operators group from left to right. Thus the following pairs of lines are equivalent
Note that the bitwise operators have higher precedence than the comparison operators in Toit. This is like Dart, but unlike C and Java. Thus the following are equivalent in Toit, but not in C:
See the full documentation of operators on
int
and
float
.
Type operators
The next operators in the precedence order are the type operators, is
, is not
, and as
.
is
and is not
return a boolean depending on whether the runtime type
of the left hand side has the type on the right.
as
is a cast, which dynamically checks the type and returns a value with the
correct static type:
my-function x y: if x is int: // Code that can use x as an integer if y is not string: // y must be something other than string // The programmer knows somehow that y is a number. This checks // that the programmer is right and allows the program to pass the // compiler's type checker: return (y as num) + 1
Comparison operators
The next operators in the precedence order are the comparison operators,
==
!=
<
<=
>
>=
.
Thus the following pairs of lines are equivalent:
c3 := x + 1 <= y * 2 c4 := (x + 1) >= (y * 2) c5 := x + 1 <= y & -2 c6 := (x + 1) >= (y & -2) // Equivalent in Toit, not in C.
The comparison operators form a group with ternary and other n-ary forms so that they can be used in the mathematical way. Thus the following pairs are equivalent:
// Test that x is in the range [0, y). if 0 <= x < y: // ... if 0 <= x and x < y: // ... // Test that 'from' and 'to' are in the range [0, size), // and that 'from' is less than or equal to 'to'. if 0 <= from <= to < size: // ... if 0 <= from and from <= to and to < size: // ... // Test that 'base' is equal to 0, and that 'level' is in the // range [0, top). if 0 == base <= level < top: // ... if 0 == base and base <= level and level < top: // ...
Calls and arguments
Calls and arguments are done with spaces in Toit, not with parentheses and commas. Thus we write:
import math show * my-function x y z: foo x y z // In C: foo(x, y, z) c7 := 2 * (sin x) // In C: 2 * sin(x)
The mathematical operators all have higher precedence than calls, so the following are equivalent:
foo x + 1 y + 2 z + 3 foo (x + 1) (y + 2) (z + 3) cos x + PI / 4 // In C: cos(x + PI / 4) cos (x + PI / 4) // In C: cos(x + PI / 4) cos(x + PI / 4) // Looks like C call syntax.
The third form, with the redundant parentheses and no space, looks like a C-style call syntax. However this is an illusion:
import math show * my-function x/float y/float: acos(x) + 100 // Not what it looks like! acos (x + 100) // Equivalent! atan2 x y // Correct. atan2(x, y) // Syntax error!
Logical operators
In descending precedence order:
not
and
or
?:
(the ternary operator)
The first three match the names used in Python and C++. In
C++ these alphabetical forms are rarely used, and
it is more common to see the symbolic forms, &&
, ||
, and !
.
The symbolic forms are not available in Toit.
The following pairs are equivalent:
my-function a b c: if not a and b == 0: // ... if (not a) and (b == 0): // ... if a == 0 or b == 0 and c == 0: // ... if (a == 0) or ((b == 0) and (c == 0)): // ...
Since the logical operators are lower than the calls and arguments we have to group them:
my-unary-function x/bool: // ... is-odd x/int -> bool: return x % 2 == 1 my-function condition-a/bool condition-b/bool x/int y/int: my-unary-function (condition-a and condition-b) // Parentheses needed. // The following two forms are equivalent. if is-odd x and is-odd y: // ... if (is-odd x) and (is-odd y): // ...
Assignment operators
The lowest operators in the precedence order are the assignment operators,
=
:=
::=
+=
-=
*=
/=
%=
|=
^=
&=
<<=
>>=
>>>=
.
Since they are below the logical operators and calls, we can write equivalently:
angle-1 := atan2 x y angle-2 := (atan2 x y) condition-1 := condition-a and condition-b condition-2 := (condition-1 and condition-b)
Assignment operators are only allowed at the start of statements and
in the first clause of a for
, if
, or while
statement.