This tutorial is still in work, any contribution is highly appreciated!
Language basics will be covered, more information can be found in the TON repo:
Basically, FunC is quite lightweight abstraction on top of TVM assembler, so if you read TVM spec you will likely get the core concepts very quickly.
#include "stdlib.fc" ;; searches for file in the FUNCPATH and workdir
There can be three types of cell:
This non-wrapped code is taken as main
entrypoint source and cannot be accessed from other cells.
Consider running a cell as a single invocation of a contract.
In order to provide a truly interactive experience, xeus-fift inspects the last line of the cell and adds return
and trailing semicolon if needed.
0x01 ^ 0x80 ;; xor
129
8 <=> -4 ;; compare (1 if equal, -1 if less, 0 otherwise)
1
~ 0xff + 0xff ;; bitwise complement
-1
min(-2, 0)
-2
max(2, 3)
3
9 ~>> 2 ;; division by 2^n using nearest-integer rounding
2
9 ^>> 2 ;; division by 2^n using ceiling rounding
3
Rounding works similarly for integer division and modular division operators:
~/
^/
~%
^%
11 /% 2 ;; extended mod division: computes both the quotient and the remainder
5 1
divmod(11, 2) ;; same effect
5 1
moddiv(11, 2) ;; change output order
1 5
int x = 11; ;; declaring variable of type `int`
x~divmod(2) ;; non-const method (starting with `~`)
5 1
int x = 42;
x += 2
44
Similar let-constructions can be made on top of with practically all integer operators:
-=
*=
/=
%=
&=
|=
<<=
>>=
^=
~>>=
^>>=
~/=
^/=
~%=
^%=
muldiv(3, 3, 2) ;; equivalent of `a * b / c`
4
muldivr(3, 3, 2) ;; equivalent of `a * b ~/ c`
5
true
-1
false
0
~ false
{-
Btw, this is a multiline comment
-}
-1
var t = (1, 2, 3); ;; packing
t
1 2 3
var (a, b, c) = (1, 2, 3); ;; unpacking
b
2
var (a, b) = (1, (2, 3));
b
2 3
var (_, x, _) = (0xab, 0xcd, 0xef); ;; one can use special operand `_` for values that won't be used
x
205
An ordered collection of up to 255 components, having arbitrary value types, possibly distinct.
May be used to represent nonpersistent values of arbitrary algebraic data types.
Nil ;; 0-tuple
[]
pair(0xdead, 0xbeaf)
[ 57005 48815 ]
second(pair(0xdead, 0xbeaf))
48815
at(pair(0xdead, 0xbeaf), 0) ;; universal helper for tuples
57005
unpair(pair(0xdead, 0xbeaf))
57005 48815
triple(1, 2, 3)
[ 1 2 3 ]
third(triple(1, 2, 3))
3
untriple(triple(1, 2, 3))
1 2 3
tuple4(-10, -20, -30, -40)
[ -10 -20 -30 -40 ]
fourth(tuple4(-10, -20, -30, -40))
-40
untuple4(tuple4(-10, -20, -30, -40))
-10 -20 -30 -40
cons(1, cons(2, cons(3, Nil)))
[ 1 [ 2 [ 3 [] ] ] ]
uncons(cons(1, cons(2, cons(3, Nil)))) ;; (head, tail)
1 [ 2 [ 3 [] ] ]
car(cons(1, cons(2, cons(3, Nil)))) ;; head
1
cdr(cons(1, cons(2, cons(3, Nil)))) ;; tail
[ 2 [ 3 [] ] ]
list_next(cons(1, cons(2, cons(3, Nil)))); ;; (tail, head)
[ 2 [ 3 [] ] ] 1
A TVM cell consists of at most 1023 bits of data, and of at
most four references to other cells.
All persistent data (including TVM
code) in the TON Blockchain is represented as a collection of TVM
cells.
The cell primitives are mostly either cell serialization primitives, which work with Builder s, or cell deserialization primitives, which work with Slices.
A TVM cell builder, or builder for short, is an “incomplete”
cell that supports fast operations of appending bitstrings and cell references at its end.
Builders are used for packing (or serializing) data
from the top of the stack into new cells (e.g., before transferring them
to persistent storage).
builder b = begin_cell() ;; two descriptor bytes come first (read more in 3.1.4. Standard cell representation)
BC{0000}
cell res = end_cell(begin_cell());
C{96A296D224F285C67BEE93C30F8A309157F0DAA35DC5B87E410B78630A09CFC7}
begin_cell().end_cell() ;; const method (dot is not a part of the function name)
C{96A296D224F285C67BEE93C30F8A309157F0DAA35DC5B87E410B78630A09CFC7}
begin_cell().store_int(-0xFF, 32) ;; we added a 32bit signed integer
BC{0008ffffff01}
begin_cell().store_uint(-42, 32)
VM error: integer out of range
cell c1 = begin_cell().store_int(1, 8).end_cell();
begin_cell().store_ref(c1) ;; first byte indicates that cell holds one reference
BC{0100}
A TVM cell slice, or slice for short, is a contiguous “sub-cell”
of an existing cell, containing some of its bits of data and some of its
references.
Essentially, a slice is a read-only view for a subcell of a cell.
Slices are used for unpacking data previously stored (or serialized) in a
cell or a tree of cells.
cell c = begin_cell().store_int(0xfa, 16).end_cell();
c.begin_parse()
CS{Cell{000400fa} bits: 0..16; refs: 0..0}
cell c = begin_cell().store_int(0xfa, 16).end_cell();
slice cs = c.begin_parse();
int res = cs~load_int(16);
res
250
cell c = begin_cell().store_uint(0xfa, 16).end_cell();
slice cs = c.begin_parse();
var (b1, b2) = (cs~load_uint(16), cs~load_uint(16)) ;; non-const method `load` reads and increases offset
VM error: cell underflow
cell c = begin_cell().store_int(0xfa, 16).end_cell();
slice cs = c.begin_parse();
cs.skip_bits(16).end_parse() ;; ensure we read the entire cell with `end_parse`
cell c = begin_cell().store_int(0xfa, 9).end_cell();
slice cs = c.begin_parse();
slice c8 = cs.skip_bits(1).first_bits(8);
c8.preload_uint(8) ;; read without increasing the offset
250
cell c1 = begin_cell().store_uint(1, 1).end_cell();
cell c2 = begin_cell().store_ref(c1).end_cell();
cell c3 = c2.begin_parse().preload_ref();
c3.begin_parse().preload_uint(1)
1
Hashmaps, or dictionaries, are a specific data structure represented by a tree of cells.
new_dict()
(null)
dict_empty?(new_dict())
-1
slice test_slice(int byte) {
cell c = begin_cell().store_uint(byte, 8).end_cell();
return c.begin_parse();
}
var d = new_dict();
d~udict_set(8, 1, test_slice(0xfa));
d.begin_parse()
CS{Cell{0005a007ea} bits: 0..22; refs: 0..0}
var d = new_dict();
d~udict_set(8, 1, test_slice(0xfa));
var (d1, _) = udict_replace?(d, 8, 1, test_slice(0xfb));
d1.begin_parse()
CS{Cell{0005a007ee} bits: 0..22; refs: 0..0}
var d = new_dict();
var (d1, found) = udict_replace?(d, 8, 1, test_slice(0xfa));
found
0
var d = new_dict();
d~udict_set(8, 1, test_slice(0xfa));
udict_delete?(d, 8, 1) ;; returns new dict and boolean flag (found or not)
(null) -1
var d = new_dict();
d~udict_set(8, 1, test_slice(0xfa));
udict_get?(d, 8, 1) ;; returns slice and boolean flag (found or not)
CS{Cell{0005a007ea} bits: 14..22; refs: 0..0} -1
Can be any combination (nested funC tuples) of builtin types int
tuple
cell
slice
builder
cont
, special "hole" type _
(type is deduced from the return value), or void (empty tuple) ()
impure
- tells the compiler that function can modify passed arguments, and prevents function invocation pruning during the optimization;inline
- works pretty the same as in C, tells the compiler to paste function body on every call occurence;inline_ref
- in case of a simple inlining, an n-bit slice of the current continuation (cell) is converted to a new continuation (code and codepage), while inline_ref is when a cell reference is converted to a continuation and pushed onto the stack (more info in 4.2.3. Constant, or literal, continuations);method_id
- declares a Get method (entrypoint) for the contract (that can be called along with the main
, recv_internal
and recv_external
)asm
( stack order of arguments
-> stack order of return values
) "INSTRUCTION#0
" "INSTRUCTION#1
" ... "INSTRUCTION#N
"
Here is a comprehensive sample function from stdlib demonstrating all possible outcomes:
(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT" "NULLSWAPIFNOT";
One can omit both or separate args/return stack ordering:
int dict_empty?(cell c) asm "DICTEMPTY";
(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF";
cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF";
forall
X0
, X1
, ... , Xn
->
forall X, Y -> tuple pair(X x, Y y) asm "PAIR";
if (true) {
print(1);
} else {
print(2);
}
1
ifnot (true) {
print(3);
} elseif (false) {
print(4);
} elseifnot (false) {
print(5);
}
5
return 0 == 0 ? 1 : 2
1
int i = 10;
while (i > 0) {
print(i);
i -= 1;
}
10 9 8 7 6 5 4 3 2 1
int i = 10;
do {
print(i);
i -= 1;
} until (i == 0)
10 9 8 7 6 5 4 3 2 1
int i = 10;
repeat (5) { i *= 10; }
i
1000000
throw(31)
VM error: unknown code 31
throw_if(32, true);
VM error: unknown code 32
throw_unless(33, false)
VM error: unknown code 33