Перейти к основному содержимому

Операторы

warning

Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @alexgton.

В этом разделе кратко рассматриваются операторы FunC, составляющие код обычных тел функций.

Операторы выражений

Наиболее распространенным типом оператора является оператор выражения. Это выражение, за которым следует ;. Описание выражения было бы довольно сложным, поэтому здесь представлен только краткий обзор. Как правило, все вложенные выражения вычисляются слева направо, за исключением перестановки стека asm, которая может определять порядок вручную.

Объявление переменной

Невозможно объявить локальную переменную, не определив ее начальное значение.

Вот несколько примеров объявлений переменных:

int x = 2;
var x = 2;
(int, int) p = (1, 2);
(int, var) p = (1, 2);
(int, int, int) (x, y, z) = (1, 2, 3);
(int x, int y, int z) = (1, 2, 3);
var (x, y, z) = (1, 2, 3);
(int x = 1, int y = 2, int z = 3);
[int, int, int] [x, y, z] = [1, 2, 3];
[int x, int y, int z] = [1, 2, 3];
var [x, y, z] = [1, 2, 3];

Переменная может быть "переобъявлена" в том же блоке. Для примера, вот правильный код:

int x = 2;
int y = x + 1;
int x = 3;

На самом деле, второе появление int x - это не объявление, а просто проверка во время компиляции того, что x имеет тип int. Таким образом, третья строка, по сути, эквивалентна простому присваиванию x = 3;.

Во вложенных областях переменная может быть действительно объявлена заново, как и в языке С. Для примера, вот правильный код:

int x = 0;
int i = 0;
while (i < 10) {
(int, int) x = (i, i + 1);
;; here x is a variable of type (int, int)
i += 1;
}
;; here x is a (different) variable of type int

Но как упоминалось в разделе глобальных переменных, глобальная переменная не может быть переопределена.

Обратите внимание, что объявление переменной — это оператор выражения, поэтому на самом деле конструкции вроде int x = 2 являются полноценными выражениями. Для примера, вот правильный код:

int y = (int x = 3) + 1;

Это объявление двух переменных x и y, равных 3 и 4 соответственно.

Подчеркивание

Подчеркивание _ используется, когда значение не требуется. Например, предположим, что функция foo имеет тип int -> (int, int, int). Мы можем получить первое возвращаемое значение и проигнорировать второе и третье, как показано ниже:

(int fst, _, _) = foo(42);

Применение функции

Вызов функции выглядит так в общепринятом языке. Аргументы вызова функции перечислены после имени функции и разделены запятыми.

;; suppose foo has type (int, int, int) -> int
int x = foo(1, 2, 3);

Но обратите внимание, что foo на самом деле является функцией одного аргумента типа (int, int, int). Чтобы увидеть разницу, предположим, что bar — это функция типа int -> (int, int, int). В отличие от обычных языков, вы можете составлять функции следующим образом:

int x = foo(bar(42));

вместо похожей, но более длинной формы:

(int a, int b, int c) = bar(42);
int x = foo(a, b, c);

Также возможны вызовы в стиле Haskell, но не всегда (будут исправлены позже):

;; suppose foo has type int -> int -> int -> int
;; i.e. it's carried
(int a, int b, int c) = (1, 2, 3);
int x = foo a b c; ;; ok
;; int y = foo 1 2 3; wouldn't compile
int y = foo (1) (2) (3); ;; ok

Лямбда-выражения

Лямбда-выражения пока не поддерживаются.

Вызовы методов

Немодифицирующие методы

Если у функции есть хотя бы один аргумент, ее можно вызвать как немодифицирующий метод. Например, store_uint имеет тип (builder, int, int) -> builder (второй аргумент — это значение для сохранения, а третий — длина бита). begin_cell — это функция, которая создает новый builder. Следующие коды эквивалентны:

builder b = begin_cell();
b = store_uint(b, 239, 8);
builder b = begin_cell();
b = b.store_uint(239, 8);

Таким образом, первый аргумент функции может быть передан ей, будучи расположенным перед именем функции, если они разделены .. Код можно еще больше упростить:

builder b = begin_cell().store_uint(239, 8);

Также возможны множественные вызовы методов:

builder b = begin_cell().store_uint(239, 8)
.store_int(-1, 16)
.store_uint(0xff, 10);

Модифицирующие методы

Если первый аргумент функции имеет тип A, а возвращаемое значение функции имеет форму (A, B), где B — некоторый произвольный тип, то функцию можно вызвать как модифицирующий метод. Модифицирующие вызовы методов могут принимать некоторые аргументы и возвращать некоторые значения, но они изменяют свой первый аргумент, то есть присваивают первый компонент возвращаемого значения переменной из первого аргумента. Например, предположим, что cs — это срез ячейки, а load_uint имеет тип (slice, int) -> (slice, int): он принимает срез ячейки и количество бит для загрузки и возвращает остаток среза и загруженное значение. Следующие коды эквивалентны:

(cs, int x) = load_uint(cs, 8);
(cs, int x) = cs.load_uint(8);
int x = cs~load_uint(8);

В некоторых случаях мы хотим использовать функцию как модифицирующий метод, который не возвращает никакого значения, а только изменяет первый аргумент. Это можно сделать с помощью типов единиц следующим образом: Предположим, мы хотим определить функцию inc типа int -> int, которая увеличивает целое число, и использовать ее как модифицирующий метод. Тогда мы должны определить inc как функцию типа int -> (int, ()):

(int, ()) inc(int x) {
return (x + 1, ());
}

При таком определении ее можно использовать как модифицирующий метод. Следующее будет увеличивать x.

x~inc();

. и ~ в именах функций

Предположим, мы хотим использовать inc также как немодифицирующий метод. Мы можем написать что-то вроде этого:

(int y, _) = inc(x);

Но можно переопределить определение inc как модифицирующего метода.

int inc(int x) {
return x + 1;
}
(int, ()) ~inc(int x) {
return (x + 1, ());
}

И затем вызвать его так:

x~inc();
int y = inc(x);
int z = x.inc();

Первый вызов изменит x; второй и третий - нет.

Подводя итог, можно сказать, что когда функция с именем foo вызывается как немодифицирующий или модифицирующий метод (т. е. с синтаксисом .foo или ~foo), компилятор FunC использует определение .foo или ~foo соответственно, если такое определение представлено, а если нет, то он использует определение foo.

Операторы

Обратите внимание, что в настоящее время все унарные и бинарные операторы являются целочисленными операторами. Логические операторы представлены как побитовые целочисленные операторы (ср. отсутствие булевого типа)).

Унарные операторы

Существует два унарных оператора:

  • ~ - побитовое Не (приоритет 75)
  • - - целочисленное отрицание (приоритет 20)

Их следует отделять от аргумента:

  • - x - допустимо.
  • -x не подходит (это одиночный идентификатор)

Бинарные операторы

С приоритетом 30 (левоассоциативный):

  • * - целочисленное умножение
  • / - целочисленное деление (floor)
  • ~/ - целочисленное деление (round)
  • ^/ - целочисленное деление (ceil)
  • % - это уменьшение целого числа по модулю (floor)
  • ~% - уменьшение целого числа по модулю (round)
  • ^% - это уменьшение целого числа по модулю (ceil)
  • /% возвращает частное и остаток
  • & - побитовое И

С приоритетом 20 (левоассоциативный):

  • + - целочисленное сложение
  • - - целочисленное вычитание
  • | - побитовое ИЛИ
  • ^ - побитовое исключение ИЛИ

С приоритетом 17 (левоассоциативный):

  • << - побитовый сдвиг влево
  • >> - побитовый сдвиг вправо
  • ~>> - побитовый сдвиг вправо (round)
  • ^>> - побитовый сдвиг вправо (ceil)

С приоритетом 15 (левоассоциативный):

  • == проверка равенства целых чисел
  • != проверка неравенства целых чисел
  • < сравнение целых чисел
  • <= сравнение целых чисел
  • > сравнение целых чисел
  • >= сравнение целых чисел
  • <=> сравнение целых чисел (возвращает -1, 0 или 1)

Их также следует отделить от аргумента:

  • x + y допустимо
  • x+y недопустимо (это один идентификатор)

Условный оператор

Имеет обычный синтаксис.

<condition> ? <consequence> : <alternative>

Например:

x > 0 ? x * fac(x - 1) : 1;

Имеет приоритет 13.

Присваивания

Приоритет 10.

Простое присваивание = и аналоги бинарных операций: +=, -=, *=, /=, ~/=, ^/=, %=, ~%=, ^%=, <<=, >>=, ~>>=, ^>>=, &=, |=, ^=.

Циклы

FunC поддерживает циклы repeat, while и do { ... } until. Цикл for не поддерживается.

Цикл повтора

Синтаксис представляет собой ключевое слово repeat, за которым следует выражение типа int. Повторяет код указанное количество раз. Примеры:

int x = 1;
repeat(10) {
x *= 2;
}
;; x = 1024
int x = 1, y = 10;
repeat(y + 6) {
x *= 2;
}
;; x = 65536
int x = 1;
repeat(-1) {
x *= 2;
}
;; x = 1

Если количество раз меньше, -2^31 или больше, 2^31 - 1, выдается исключение проверки диапазона.

Цикл с условием

Имеет обычный синтаксис. Пример:

int x = 2;
while (x < 100) {
x = x * x;
}
;; x = 256

Обратите внимание, что истинностное значение условия x < 100 имеет тип int (ср. ср. отсутствие типа boolean).

Цикл до

Имеет следующий синтаксис:

int x = 0;
do {
x += 3;
} until (x % 17 == 0);
;; x = 51

Операторы If

Примеры:

;; usual if
if (flag) {
do_something();
}
;; equivalent to if (~ flag)
ifnot (flag) {
do_something();
}
;; usual if-else
if (flag) {
do_something();
}
else {
do_alternative();
}
;; Some specific features
if (flag1) {
do_something1();
} else {
do_alternative4();
}

Фигурные скобки необходимы. Такой код не будет скомпилирован:

if (flag1)
do_something();

Операторы Try-Catch

Доступно в func с v0.4.0.

Выполняет код в блоке try. В случае сбоя полностью откатывает изменения, внесенные в блок try, и вместо этого выполняет блок catch; catch получает два аргумента: параметр исключения любого типа (x) и код ошибки (n, целое число).

В отличие от многих других языков в операторе FunC try-catch изменения, внесенные в блок try, в частности изменение локальных и глобальных переменных, все изменения регистров (т. е. регистра хранения c4, регистра действия/сообщения c5, регистра контекста c7 и других) отменяются, если в блоке try есть ошибка, и, следовательно, все обновления хранилища контрактов и отправка сообщений будут отменены. Важно отметить, что некоторые параметры состояния TVM, такие как codepage и счетчики газа, не будут откатываться. Это означает, в частности, что весь газ, потраченный в блоке try, будет учтен, а эффекты OP, которые изменяют лимит газа (accept_message и set_gas_limit), будут сохранены.

Обратите внимание, что параметр исключения может быть любого типа (возможно, разного в случае разных исключений), и поэтому funC не может предсказать его во время компиляции. Это означает, что разработчику нужно "помочь" компилятору, приведя параметр исключения к некоторому типу (см. пример 2 ниже):

Примеры:

try {
do_something();
} catch (x, n) {
handle_exception();
}
forall X -> int cast_to_int(X x) asm "NOP";
...
try {
throw_arg(-1, 100);
} catch (x, n) {
x.cast_to_int();
;; x = -1, n = 100
return x + 1;
}
int x = 0;
try {
x += 1;
throw(100);
} catch (_, _) {
}
;; x = 0 (not 1)

Операторы блока

Операторы блока также разрешены. Они открывают новую вложенную область:

int x = 1;
builder b = begin_cell();
{
builder x = begin_cell().store_uint(0, 8);
b = x;
}
x += 1;