Language Guide¶
Program Structure¶
A Tealish program has the following structure:
#pragma version 6
{Comments}
{Struct Definitions}
{Constant Declarations}
{Statements}
{Exit Statement}
{Blocks}
{Function Definitions}
Literals¶
Literal bytes:
"abc"
Literal Integers (unsigned):
42
Constants¶
Constants must use UPPERCASE names:
const int FOO = 1
const bytes BAR = "ABC"
Declaration & Assignment¶
Variable names must begin with a lowercase letter: [a-z][a-zA-Z0-9_]*
.
Variables can be declared separately from assignment:
int x
bytes b
x = 1
b = "abc"
Or declared and assigned together:
int x = 1
bytes b = "abc"
Assignments must be of the form: {name} = {expression}
.
The spacing around the =
operator is mandatory.
Some opcodes return multiple values. In this case the variables must be declared first:
int exists
bytes asset_unit_name
exists, asset_unit_name = asset_params_get(AssetUnitName, asset_id)
Sometimes you will want to ignore one of the return values:
_, asset_unit_name = asset_params_get(AssetUnitName, asset_id)
Expressions¶
Expressions are pieces of Tealish that evaluate to a value. They are not valid statements on their own but are used on the right side of assignments, in conditions and arguments.
Note
Statements vs Expressions
A Tealish statement is a valid standalone line (or lines) of Tealish. An expression is something that evaluates to a value. It must be used inside a statement.
- Expressions are composed of the following components:
Values
Function Calls
Math/Logic
Expression Groups
Values¶
- Values are composed of:
Literals
Constants
Variables
Fields
Builtins
Function Calls¶
Function calls take the form of {name}({expression}, {expression}...)
.
Functions can take 0, 1 or more arguments.
Examples:
sqrt(x)
app_local_get(address, key)
Function calls are expressions so they are valid arguments to other functions:
sqrt(sqrt(x))
The same function call syntax is used for opcodes, user defined functions and Tealish builtins. Functions that do not return values cannot be used as expressions. They can only be used as statements.
OpCodes¶
Most opcodes (AVM Functions) can be used in a function call style. The syntax is of the following form: {op_name}({expression_A}, {expression_B}...)
.
For example app_local_get
is described in the docs as local state of the key B in the current application in account A
.
In Tealish this is written as app_local_get(account, key)
.
Some opcodes expect “immediate args”. For example substring
takes two immediate arguments s
(start), e
(end). In Tealish this is written as substring(0, 5)
.
Some opcodes expect both immediate and stack arguments: e.g asset_params_get(AssetUnitName, asset_id)
. It is important to note that immediate arguments cannot be expressions and therefore must be literals.
See Functions for the full list of supported function opcodes.
Some opcodes are defined for use as mathematical or logical operators. These are used in the form discussed below.
Math/Logic¶
Math & Logic are most naturally written with infix notation. Nearly all math & logic expressions are binary expressions of the form {expression} {operator} {expression}
.
Spacing around the operator is required.
Examples:
1 + 2
2 * a
b / 2
1 == 2
2 != a
x > y
As math & logic expressions are all binary expressions it is necessary to use “groups” rather than chaining:
1 + 2 + 3 # Invalid!
(1 + 2) + 3 # Valid
1 + (2 + 3) # Valid
This requirement exists to ensure math and logic is written obviously and unambiguously (from a human reader perspective).
The exception to the above rule is Unary expressions:
!x # valid
x || !y # valid
Note
Logical operators ||
(or) and &&
(and) in Tealish have AVM stack semantics which differ from some other languages.
There is no short circuiting with these operators.
For example x && f(x)
will still evaluate both x
and f(x)
before evaluating the &&
even if x
is 0.
See Operators for the full list of supported math & logic operator opcodes.
Note
Operators are not handled the same way as other opcodes in Tealish so the langspec opcode support mechanisms do not apply.
Fields¶
The AVM provides readonly access to fields in a number of namespaces through opcodes. These include global, txn, txna, gtxn, gtxnas, itxn,
and many more.
Tealish provides a specific syntax for accessing fields to differentiate these from function calls and make the code more readable. The base form of this syntax is {Namespace}.{FieldName}
.
The namespace is always the Capitalized version of the corresponding (base) opcode; e..g Txn, Gtxn, Itxn, Global
. Fieldnames are also always Capitalized.
For array fields indexing is used to access specific elements: {Namespace}.{FieldName}[{Expression}]
. In this case the base opcode is still used as the namespace (e.g Txn instead of Txna).
Fields from other transactions in the same Group can be accessed with Gtxn[{GroupIndex}].{FieldName}[{Expression}]
.
GroupIndex can be an expression or a signed literal (e.g +1, -2). Signed literals are used for relative indexing.
Examples:
Global.Round
Txn.Sender
Itxn.Fee
Txn.ApplicationArgs[0]
Txn.ApplicationArgs[x + 2]
Gtxn[0].Sender
Gtxn[payment_txn_index].Receiver
Gtxn[+1].ApplicationArgs[0]
Gtxn[-2].Sender
See Fields for the full list of fields.
If/Elif/Else¶
Structure:
if {condition_Expression}:
{Statements}
elif {condition_Expression}:
{Statements}
else:
{Statements}
end
Examples:
if x < 1:
result = 1
elif x < 10:
result = 2
else:
error()
end
if x == y:
result = 1
end
if x < 1:
result = 0
else:
result = 1
end
If statements can be nested:
if x:
if y:
error()
end
end
While Loop¶
Structure:
while {condition_Expression}:
{Statements}
end
Examples:
int i = 0
while i <= 10:
result = result + Txn.ApplicationArgs[i]
i = i + 1
end
For Loop¶
Structure:
for {name} in {start}:{end}
{Statements}
end
start and end can be Literals or Variables but not Expressions (for readability).
Examples:
for i in 0:10:
result = result + Txn.ApplicationArgs[i]
end
int start = 0
int end = start + 10
for i in start:end:
result = result + Txn.ApplicationArgs[i]
end
# if the loop variable is not used in the body then ``_`` should be used instead.
for _ in 0:10:
result = result + "*"
end
Inline Teal¶
Structure:
teal:
{Statements}
end
Examples:
pushint(6)
teal:
int 7
*
int 10
*
end
int result = pop()
Inner Transactions¶
Tealish has a special syntax for Inner Transactions:
inner_txn:
{FieldName}: {Expression}
{FieldName}: {Expression}
...
end
Example:
inner_txn:
TypeEnum: Pay
Receiver: Txn.Sender
Amount: 1000
Fee: 0
end
Inner transactions are evaluated immediately so there is no separate submit function.
Inner transactions can be grouped in inner groups:
inner_group:
inner_txn:
TypeEnum: Pay
Receiver: Txn.Sender
Amount: 1000
Fee: 0
end
inner_txn:
TypeEnum: Axfer
AssetReceiver: Txn.Sender
AssetAmount: 1000
Index: 5
Fee: 0
end
end
Functions¶
Functions can be defined in Tealish in the following forms:
func {func_name}({arg1_name}: {type}, {arg2_name}: {type}) {return_type}:
{Statements}
return {Expression}
end
# No return value
func {func_name}({arg1_name}: {type}, {arg2_name}: {type}):
{Statements}
return
end
# No return value or arguments
func {func_name}():
{Statements}
return
end
# Multiple return values
func {func_name}() {return_type}, {return_type}:
{Statements}
return {Expression}, {Expression}
end
# Returns in if & else statements
func {func_name}() {return_type}, {return_type}:
{Statements}
if {Statements}:
return {Statements}
else:
return {Statements}
end
# Return is mandatory just before the end statement of the function
return
end
Notes:
Function names must be lowercase.
Argument names must be lowercase.
Types must be
int
orbytes
.Functions must have
return
just beforeend
.Functions must be defined at the end of programs or Blocks. There can be no other statements after function definitions apart from other function definitions.
Examples:
func get_balance(account_idx: int, asset_id: int) int:
int balance
if asset_id == 0:
balance = balance(account_idx) - min_balance(account_idx)
else:
_, balance = asset_holding_get(AssetBalance, account_idx, asset_id)
end
return balance
end
func checks():
assert(app_local_get(0, "x") > 7)
assert(app_local_get(0, "y") > 6)
end
Blocks¶
Blocks can be defined in Tealish in the following forms:
block {block_name}:
{Statements}
end
Notes:
Variables are scoped by blocks and functions.
Blocks should end with an exit statement.
Examples:
block main:
int sender = Txn.Sender
pay_to_user()
exit(1)
func pay_to_user(amount: int):
# Function can use variables defined in the outer blocks.
pay(sender, amount)
return
end
exit(0)
end
func pay(receiver: bytes, amount: int):
inner_txn:
TypeEnum: Pay
Receiver: receiver
Amount: amount
Fee: 0
end
return
end
Switch¶
A switch statement is used to conditionally branch to blocks of a program. Structure:
switch {condition_Expression}:
{Value}: {Block_Name}
{Value}: {Block_Name}
{Value}: {Block_Name}
...
else: {Block_Name}
end
Notes:
If none if the values match the expression then the optional
else
target is used if specified, otherwise an error is raised.Any code after a
switch
is unreachable.
Example:
switch Txn.OnCompletion:
NoOp: main
OptIn: opt_in
CloseOut: close_out
UpdateApplication: update_app
DeleteApplication: delete_app
end
block opt_in:
# Handle Opt In
# some statements here
exit(1)
end
block update_app:
# Handle Update App
# Example: Only allow the Creator to update the app
exit(Txn.Sender == Global.CreatorAddress)
end
Jump¶
A jump statement is used to unconditionally branch to a block of a program.
Structure:
jump({Block_Name})
Notes:
The block name is a literal, not an expression.
Any code after a
jump
is unreachable.
Structs¶
Structs are used to define structure for byte strings.
Structs can be defined in Tealish in the following form:
struct {Struct_name}:
{field_name}: {type}
end
Notes:
Structs must be defined at the top of the file.
Struct names must begin with a capital letter.
Field names must be lowercase.
Types may be either
int
orbytes[N]
Examples:
struct Item:
x: int
y: int
name: bytes[10]
end
Item item1 = Txn.ApplicationArgs[0]
log(item.name)
assert(item.x > 10)
item.y = 1
Warning
There are no implicit runtime checks when assigning to a struct or struct field. Care must be taken to ensure field values are correctly sized.
Boxes¶
Boxes can be accessed and manipulated using the standard opcodes (box_put
, box_get
, box_extract
, etc).
Tealish also supports a higher level syntax that makes it easier to deal with structured data in boxes using structs. A typed box reference can created with the following forms:
box<{Struct_name}> {box_name} = CreateBox("{box_key}") # asserts box does not already exist
box<{Struct_name}> {box_name} = OpenBox("{box_key}") # asserts box does already exist and has the correct size for the struct
box<{Struct_name}> {box_name} = Box("{box_key}") # makes no assertions about the box
A box field can be set or accessed just like a struct field:
{box_name}.{field_name} = {value}
log({box_name}.{field_name})
Examples:
struct Item:
id: int
foo: int
name: bytes[10]
end
box<Item> item1 = CreateBox("a")
item1.id = 1
item1.foo = 111
item1.name = "Tealish "
log(item1.name)
Warning
There are no implicit runtime checks when assigning to a box field. Care must be taken to ensure field values are correctly sized.
Comments¶
All comments begin on a new line and start with
#
:# A comment