# Операторы

Ниже описана логика работы операторов при использовании их с различными типами данных. Для бинарных операций левый операнд обозначается символом x, правый - символом y; для унарных операторов операнд обозначается символом x. При выполнении операций могут выполняться неявные преобразования и приведения типов. Если невозможно ни преобразовать, ни привести значения к подходящим для данного оператора типам, возникает исключение.

# Неявные преобразования типов

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

  • в Int32 преобразуются

    • Time

    • Int64 (если его значение находится в пределах диапазона значений Int32)

    • Boolean (true преобразуется в 1_i32, false преобразуется в 0_i32)

  • в Int64 преобразуются

    • Int32

    • Time

    • Boolean (true преобразуется в 1_i64, false преобразуется в 0_i64)

  • в Real преобразуются

    • Int32

    • Int64 (если его значение находится в пределах диапазона значений, которые могут быть представлены с помощью Real)

    • Time

    • Money

    • Boolean (true преобразуется в 1, false преобразуется в 0)

  • в Boolean преобразуются

    • Nil (преобразуется в false)

    • Int32, Int64, Real, Money (ноль преобразуется в false, остальные значения - в true)

    • Time (00:00:00 преобразуется в false, остальные значения - в true)

    • Date, DateTime (null date и null datetime преобразуются в false, остальные значения - в true)

    • String (пустая строка преобразуется в false, непустая - в true)

    • Bytes (пустая коллекция преобразуется в false, непустая - в true)

    • все непримитивные типы данных (преобразуются в true)

# Неявные приведения типов

Приведением типов называется операция, в результате которой тип значения меняется с возможной потерей данных (например, при приведении Real к Int32 отбрасывается дробная часть). Любой тип данных может быть приведён к самому себе. Могут также выполняться следующие виды приведения:

  • к Int32 могут быть приведены

    • все типы, которые преобразуются в Int32

    • все типы, которые преобразуются в Real (при этом выполняется приведение x -> Real -> Int32)

  • к Int64 могут быть приведены

    • все типы, которые преобразуются в Int64

    • все типы, которые преобразуются в Real (при этом выполняется приведение x -> Real -> Int64)

  • к Time могут быть приведены все типы, которые приводятся к Int32

  • к Date могут быть приведены

    • DateTime (c отбрасыванием времени)

    • Nil (приводится к null date)

    • все типы, которые приводятся к Int64: 0_i64 приводится к null date, любое другое значение вызывает исключение

  • к DateTime могут быть приведены

    • Date (результатом будет DateTime, в котором часы, минуты, секунды и миллисекунды равны 0)

    • Nil (приводится к null datetime)

    • все типы, которые приводятся к Int64: 0_i64 приводится к null datetime, любое другое значение вызывает исключение

  • приведение к String работает так же, как и функция-конструктор string

  • к типу Bytes могут быть приведены все типы, которые приводятся к String; результатом будет последовательность UTF-8 байт строкового представления значения

# Оператор сложения +

Оператор сложения является коммутативным для всех типов операндов кроме строк, т.е. порядок операндов в нем не имеет значения. Сложение выполняется следующим образом:

  • если x является Bytes, то y приводится к Bytes, и затем выполняется конкатенация последовательностей байтов

  • если x является String, то y приводится к String, и затем выполняется конкатенация строк

  • если x является DateTime, а y

    • является Time, то к DateTime добавляется y секунд

    • приводится к Int32, то к DateTime добавляется y дней

  • если x является Date, а y

    • является Time, то результатом является DateTime, дата для которого берётся из x, а время - из y

    • приводится к Int32, то к дате добавляется y дней

  • если x является Time, то y приводится к Time, и затем они складываются

  • если x является Int64, а y преобразуется в Int64, то результатом будет сумма двух Int64

  • если x является Int32, а y преобразуется в Int32, то результатом будет сумма двух Int32

  • иначе x и y приводятся к Real и

    • если хотя бы один из операндов имеет тип Money, то результатом будет сумма двух Money

    • иначе результатом будет сумма двух Real

Если результат сложения целых чисел приводит к переполнению, возникает исключение.

Примеры:

Bytes(1) + Bytes(2)                 # Bytes(1, 2)
1 + Bytes("a")                      # Bytes("1a")
1 + "1";                            # "11"
DateTime(01.01.01) + 02:02:02;      # DateTime(01.01.01, 02:02:02)
DateTime(01.01.01) + 1;             # DateTime(02.01.01)
01.01.01 + 02:02:02;                # DateTime(01.01.01, 02:02:02)
01.01.01 + 1;                       # 02.01.01
01:01:01 + 00:00:09;                # 01:01:10
1_i64 + 1_i32;                      # 2_i64
1_i32 + 1_i32;                      # 2_i32
Money(1) + 2;                       # Money(3)
1 + 1;                              # 2
1 + 1_i32;                          # 2

# Оператор вычитания -

Вычитание выполняется следующим образом:

  • если x является DateTime, а y

    • является Date или DateTime, то результатом будет Int32 - количество календарных дней между этими датами (положительное или отрицательное)

    • является Time, результатом будет DateTime - x минус y секунд

    • приводится к Int32, то результатом будет DateTime - x минус y дней

  • если x является Date, а y

    • является Date или DateTime, то результатом будет Int32 - количество полных календарных дней между этими датами (положительное или отрицательное)

    • является Time, то x приводится к DateTime, и затем из полученного значения вычитается y секунд

    • приводится к Int32, то из x будет вычтено y дней

  • если x является Time, то y приводится к Time, и эти значения вычитаются друг из друга

  • если x является Int64, а y преобразуется в Int64, то результатом будет разность двух Int64

  • если x является Int32, а y преобразуется в Int32, то результатом будет разность двух Int32

  • иначе x и y приводятся к Real и

    • если хотя бы один из операндов имеет тип Money, то результатом будет разность двух Money

    • иначе результатом будет разность двух Real

Если результат вычитания целых чисел приводит к переполнению, возникает исключение.

Примеры:

DateTime(03.01.01) - DateTime(01.01.01);        # 2_i32
DateTime(02.01.01) - 00:00:01;                  # DateTime(01.01.01, 23:59:59)
DateTime(02.01.01) - 1;                         # DateTime(01.01.01)
03.01.01 - DateTime(01.01.01);                  # 2_i32
02.01.01 - 00:00:01;                            # DateTime(01.01.01, 23:59:59)
02.01.01 - 1;                                   # 01.01.01
00:00:10 - 00:00:01;                            # 00:00:09
2_i64 - 1_i32;                                  # 1_i64
2_i32 - 1_i32;                                  # 1_i32
Money(2) - 1;                                   # Money(1)
2 - 1;                                          # 0
2 - 1_i32;                                      # 1

# Оператор умножения *

Оператор умножения является коммутативным для всех типов операндов. Умножение выполняется следующим образом:

  • если x имеет тип Time, а y приводится к Int32, то результатом будет Time равный количеству секунд из x умноженному на y

  • если x является Int64, а y преобразуется в Int64, то результатом будет произведение двух Int64

  • если x является Int32, а y преобразуется в Int32, то результатом будет произведение двух Int32

  • иначе x и y приводятся к Real и

    • если x имеет тип Money, а y - Real или наоборот, то результат будет иметь тип Money

    • иначе результатом будет произведение двух Real

Если при умножении целых чисел происходит переполнение, возникает исключение.

Примеры:

00:00:05 * 2;           # 00:00:10
2_i64 * 2_i64;          # 4_i64
2_i32 * 2_i32;          # 4_i32
Money(2) * 2;           # Money(4)
2 * 2;                  # 4
2_i32 * 2;              # 4

# Оператор деления /

Деление выполняется следующим образом:

  • если x имеет тип Time, а y приводится к Int32, то результатом будет Time равный частному от деления количества секунд из x на y

  • если x является Int64, а y преобразуется в Int64, то результатом будет частное от деления двух Int64

  • если x является Int32, а y преобразуется в Int32, то результатом будет частное от деления двух Int32

  • иначе x и y приводятся к Real и

    • если хотя бы один из операндов имеет тип Money, то результатом будет частное от деления двух Money

    • иначе результатом будет частное от деления двух Real

При попытке деления на ноль возникает исключение.

Примеры:

00:00:10 / 2;           # 00:00:05
4_i64 / 2_i64;          # 2_i64
4_i32 / 2_i32;          # 2_i32
Money(4) / 2;           # Money(4)
4 / 2;                  # 2
4_i32 / 2;              # 2

# Оператор остатка от деления %

Взятие остатка от деления выполняется следующим образом:

  • если x имеет тип Time, а y приводится к Int32, то результатом будет Time равный остатку от деления количества секунд из x на y

  • если x является Int64, а y преобразуется в Int64, то результатом будет остаток от деления двух Int64

  • если x является Int32, а y преобразуется в Int32, то результатом будет остаток от деления двух Int32

  • иначе x и y приводятся к Real и

    • если x имеет тип Money, а y - Real, то результат будет иметь тип Money

    • иначе результатом будет остаток от деления двух Real

При попытке деления на ноль возникает исключение.

Примеры:

5_i64 % 2_i64;          # 1_i64
5_i32 % 2_i32;          # 1_i32
Money(5) % 2;           # Money(1)
5 % 2;                  # 1
5_i32 % 2;              # 1

# Оператор унарный минус -

Этот оператор может быть применён к типам Int32, Int64, Time, Real, Money и Boolean. Для числовых типов данных этот оператор эквивалентен умножению на -1. Для Boolean унарный минус эквивалентен логическому отрицанию (оператор !).

Если применение этого оператора вызывает переполнение целочисленного типа, возникает исключение.

Примеры:

-00:00:05;        # -00:00:05
-true;            # false

# Операторы сравнения (==, !=, <, <=, >, >=)

Названия операторов:

  • == - равно

  • != - не равно

  • < - меньше

  • <= - меньше или равно

  • > - больше

  • >= - больше или равно

Основными операторами являются == и <, все остальные могут быть сведены к этим двум:

x != y   --->   !(x == y)
x <= y   --->   (x < y) || (x == y)
x > y    --->   !(x < y) && !(x == y)
x >= y   --->   !(x < y)

Результатом всех операций сравнения является Boolean.

Значение nil равно самому себе, а также null date и null datetime, и меньше любого другого значения:

nil == nil;                      # true
nil < nil;                       # false
nil == ``;                       # false
nil < ``;                        # true

nil == 00.00.0000                # true
nil == DateTime(00.00.0000)      # true

nil == 01.01.2000                # false
nil == DateTime(01.01.2000)      # false

Значение ссылочного типа (кроме Bytes) равно только самому себе:

var a = @[]; a == a;             # true, массив равен самому себе
var a = @[]; var b = a; a == b;  # true, две ссылки на один и тот же массив

@[] == @[];                      # false, сравниваются два разных массива
@[] == 1;                        # false, значение ссылочного типа не равно примитивному

Отношение порядка для ссылочных типов (кроме Bytes) не определено и вызывает исключение:

@[] <= 1;                        # исключение, отношение порядка не определено
@[] <= @[];                      # исключение, отношение порядка не определено

Если оба операнда не nil и не ссылочные (кроме Bytes), то они приводятся к одинаковому типу:

  • если один из операндов имеет тип Bytes, то второй операнд приводится к типу Bytes

  • если один из операндов имеет тип String, то второй операнд приводится к String

  • если один из операндов имеет тип DateTime, то второй операнд приводится к DateTime

  • если один из операндов имеет тип Date, то второй операнд приводится к Date

  • если один из операндов имеет тип Time, то второй операнд приводится к Time

  • если оба операнда могут быть преобразованы в Int64, оба преобразуются в Int64

  • если один из операндов имеет тип Money, то второй приводится к Money

  • иначе оба операнда приводятся к типу Real

Если приведение выполнить не удалось, то возникает исключение.

Критерии равенства значений x и y (x == y):

  • String, Bytes: x и y имеют одинаковую длину и содержат одинаковые байты в одинаковом порядке (побайтовое равенство)

  • DateTime: все поля (год, день, месяц и т.д.) x совпадают с теми же полями y (т.е. оба обозначают один и тот же момент времени с точностью до миллисекунд)

  • Time, Date, Int64 сравниваются как целые числа

  • Money: абсолютная величина разности между x и y менее 0.005 (т.е. Abs(x - y) < 0.005)

  • Real: абсолютная величина разности между x и y менее 0.000000001 (т.е. Abs(x - y) < 0.000000001)

Критерии того, что значение x меньше y (x < y):

  • String, Bytes: используется побайтовое лексикографическое упорядочение

    • если все байты на позициях 1..n в x равны соответствующим байтам y, а байт n + 1 меньше, чем соответствующий байт y

    • x является префиксом y и короче чем y

  • DateTime: момент времени x был раньше чем y

  • Time, Date, Int64 упорядочены как целые числа

  • Money, Real сравниваются как вещественные числа

Примеры:

"a" == "a";                                  # true
"ab" < "ac";                                 # true
"abc" < "abcd";                              # true

DateTime(01.02.03) == DateTime(01.02.03);    # true
DateTime(01.02.03) < DateTime(01.02.04);     # true

01.02.03 == 01.02.03;                        # true
01.02.03 < 01.02.04;                         # true

01:02:03 == 01:02:03;                        # true
01:02:03 < 01:02:03;                         # true

1 == 1;                                      # true
1 < 2;                                       # true

# Операторы инкремента и декремента (++ и --)

Операторы инкремента и декремента могут быть применены только к lvalue (напр., имени переменной) следующих типов:

  • Int32

  • Int64

  • Time

  • Date

  • Money

  • Real

  • DateTime

Оператор ++ добавляет 1 к значению своего операнда, а оператор -- - вычитает. Затем новое значение присваивается операнду. Применительно к DateTime это означает, что значение увеличивается/уменьшается на один день.

Операторы имеют две формы: префиксную ++x и постфиксную x++. Эти формы отличаются результатом, который возвращает оператор. В префиксной форме результатом будет увеличенное/уменьшенное значение операнда, а в постфиксной - исходное значение.

Если применить один из этих операторов к неопределённому ранее lvalue (напр., несуществующей переменной), то этому lvalue будет присвоено значение 0, а затем будет выполнен оператор.

Если применение оператора вызвало целочисленное переполнение, возникает исключение.

Ниже приведён эквивалентный код префиксного и постфиксного оператора инкремента:

# y = ++x
if (!exists(x))
   x = 0
x = x + 1
y = x


# y = x++
if (!exists(x))
   x = 0
t = x
x = x + 1
y = t

Примеры:

x = 00:00:01;
y = x++;             # y == 00:00:01, x == 00:00:02

x = 00:00:01;
y = --x;             # y == 00:00:00, x == 00:00:00

# Битовые операторы (&, |)

Оператор & выполняет побитовое И двух числовых значений, оператор | выполняет побитовое ИЛИ. Операторы выполняют приведение типов своих операндов следующим образом:

  • если один из операндов имеет тип Int64, то второй операнд приводится к Int64; тип результата будет Int64

  • иначе оба операнда приводятся Int32; тип результата будет Int32

Пример:

1 | 2;         # 3
1 & 2;         # 0

# Логические операторы (!, && and и, || or или)

Оператор ! - "логическое отрицание". Ключевые слова и, and, или, or, как и все остальные ключевые слова, являются регистронезависимыми.

Логические операторы преобразуют свои операнды в Boolean, затем применяют к полученным значениям соответствующую логическую функцию. Результатом выполнения логических операторов является тип Boolean. В первой версии языка операторы && и || не реализуют "короткое замыкание", т.е. они всегда вычисляют свой второй аргумент:

a() && b();    # будут вызваны обе функции, даже если вызов a() вернул false

В последующих версиях языка короткое замыкание используется:

version 2      # версия больше 1

a() && b();    # если a() вернула false, то b() не будет вызвана
a() || b();    # если a() вернула true, то b() не будет вызвана

Также благодаря короткому замыканию стало возможно следующее:

version 2

1 && 0 && 3;  # вернёт первое значение преобразуемое в false, то есть 0

1 && 2 && 3;  # если все значения преобразуются в true, то вернёт последнее,
              # то есть 3

0 || 2 || 3       # вернёт первое значение преобразуемое в true, то есть 2

false || nil || 0 # если все значения преобразуются в false, то вернётся
                  # последнее, то есть 0

# Оператор присваивания (=)

Этот оператор присваивает значение своего правого операнда левому операнду. Результатом выполнения оператора присваивания является значение его правого операнда. Присваивание несуществующей переменной создаёт переменную в окружении (environment). Присваивание ключу в коллекции создаёт/заменяет значение этого ключа. Присваивание свойству (если его значение можно менять) вызывает обновление значения этого свойства.

Примеры:

x = 1;         # создаёт в окружении переменную с именем x и значением 1

c = @{}
c.x = 1        # создаёт в коллекции c пару "x" -> 1

# Составные операторы присваивания (+=, -=, *=, /=, %=)

Эти операторы эквивалентны следующему выражению:

# x <op>= y
if (!exists(x))
   x = <default-value-of-type>(y);
x = x <op> y

где <op> - +, -, *, / или %. Таким образом они выполняют операцию <op> над текущим значением x и y, и результат выполнения операции записывают в x.

Если значение x не определено (напр., это неопределённая переменная), то перед выполнением операции x будет присвоено значение по умолчанию того же типа, что и тип y. Ниже перечислены все типы, которые имеют значение по умолчанию:

  • Nil (nil)

  • Boolean (false)

  • Int32 (0_i32)

  • Int64 (0_i64)

  • Real (0)

  • Money (Money(0))

  • String ("")

  • Time (00:00:00)

  • Date (00.00.00)

  • DateTime (null datetime)

Если тип y не имеет значения по умолчанию, возникает исключение.

Примеры:

var x = 1
x += 2;        # 3

var o = @{}
o.x += 1;      # 1, o.x был инициализирован значением по умолчанию для типа Real

# Тернарный оператор ?:

Тернарный оператор имеет следующий синтаксис:

<условие> ? <выражение 1> : <выражение 2>

Оператор работает следующим образом. Сначала вычисляется значение выражения <условие>, затем это значение приводится к Boolean. Если оно равно true, то вычисляется <выражение 1>, и значение этого выражения становится результатом оператора. Иначе вычисляется <выражение 2>, и его значение становится результатом оператора. Пример:

x = true ? 1 : 2;          # x == 1

# Оператор доступа по ключу/индексу (индексации, [] и .)

Оператор индексации имеет две основные формы:

<выражение> '.' <ключ>
<выражение> '[' <ключ-1> [',' <ключ-2>] ... [','] ']'

Он имеет два аргумента: <выражение>, результат вычисления которого должен быть коллекцией или любым другим объектом, поддерживающим оператор индексации, и <ключ>.

В первой форме ключ должен быть идентификатором, он автоматически приводится к строке.

Во второй форме ключи могут быть произвольными выражениями, автоматическое приведение не выполняется (но её может выполнять, например, конкретный экземпляр коллекции). Если количество ключей больше одного, то такая форма разворачивается в последовательность операторов индексации с одним ключом:

<выражение> '[' <ключ-1> ']' '[' <ключ-2> ']' ... '[' <ключ-N> ']'

Оператор вычисляет выражение, и извлекает из результата этого выражения (т.е. индексируемого объекта) значение, связанное с ключом. Если ключ не связан ни с каким значением, результат оператора будет Empty, и использование этого результата приведёт к исключению. Если в результате вычисления выражения было получено значение, не поддерживающее индексацию (например, число), то это также приводит к исключению. Примеры:

func f()
{
   return @{x: 1, y: 2};
}

var v = f().x;
var w = f()["x"];

z = @(1: "v");             # HashMap приведёт ключ 1 к строке
z.1;                       # "v", ключ является строкой, не числом

Поддерживается специальная форма оператора индексации:

' [ '.' <идентификатор-1> ('.' | '>') <идентификатор-1> [('.' | '>') <идентификатор-2>] ... '

Её можно использовать, если идентификатор содержит пробелы или другие символы, которые недопустимы для идентификаторов без одинарных кавычек. Пример:

var 'var with spaces' = @{
   'key with spaces 1': @{
      'key with spaces 2': 1
   }
};

'var with spaces.key with spaces 1.key with spaces 2';      # 1

Вместо точки в этой форме оператора можно также использовать символ >:

'var with spaces>key with spaces 1>key with spaces 2';      # 1

При этом до первого появления символа > точка . считается разделителем идентификаторов, а после - частью идентификатора, т.е. перестает быть специальным символом до конца оператора. Например

'a.b>c.d>e.f'

эквивалентно

a["b", "c.d", "e.f"]

# Оператор получения значения по пути к нему ([])

Этот оператор позволяет получить значение по указанному пути к этому значению. Он имеет следующий синтаксис:

'[' <выражение> ']'

Результатом вычисления выражения должна быть строка или значение, которое можно привести к строке. Строка должна иметь формат:

['.'] <идентификатор> ['.' <идентификатор>] ...

Затем эта строка вычисляется так, как если бы это был оператор индексации в специальной форме с одинарными кавычками. Т.е. оператор рассматривает свой операнд как путь к значению в графе индексируемых объектов.

Пример:

var x = @["11", "22"]
var y = [`x.1`];        # "11"

# Оператор spread (...)

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

func f(x, y, z)
{
   return x + y + z
}

var ary = @[1, 2, 3]
f(...ary)                  # то же самое, что f(1, 2, 3)
var copy = @[...ary]       # то же самое, что @[1, 2, 3]

Оператор ... можно комбинировать с любой последовательностью значений:

func f(x, y, z, w)
{
   return x + y + z + w
}

var ary = @[2, 3]
f(1, ...ary, 4)            # то же самое, что f(1, 2, 3, 4)

а также можно использовать его несколько раз в пределах последовательности:

func f(x, y, z, w)
{
   return x + y + z + w
}

var a1 = @[1, 2]
var a2 = @[3, 4]
f(...a1, ...a2)            # то же самое, что f(1, 2, 3, 4)

Этот оператор может быть полезен, если необходимо передать в функцию заранее неизвестное количество аргументов:

func g(...)
{
   return параметры.1 + параметры.2
}

func f(...)
{
   return g(...параметры)
}

f(1, 2)                    # функция g будет вызвана с аргументами 1, 2

объединить несколько коллекций в одну:

var x = @[1, 2]
var y = @[3, 4]

var z = [...x, ...y]       # @[1, 2, 3, 4]

и прочих подобных случаях.

Последовательность, которую создает оператор ... из своего аргумента, зависит от контекста, в котором он выполняется. Существует два контекста:

  • список: в вызове функции, литерале Array и литерале HashSet

  • ключ-значение: в литерале Object и в литерале HashMap

В контексте список оператор создает последовательность отдельных значений:

var ary = @[1, 2, 3]
var obj = @{1: "a", 2: "b", 3: "c"}

# создает последовательность 1, 2, 3
f(...ary)
@[...ary]
Set(...ary)

# создает последовательность "a", "b", "c"
f(...obj)
@[...obj]
Set(...obj)

В контексте ключ-значение создается последовательность пар ключ-значение:

var ary = @["a", "b", "c"]
var obj = @{x: 1, y: 2, z: 3}

# создает последовательность 1: "a", 2: "b", 3: "c"
@{...ary}
@(...ary)

# создает последовательность "x": 1, "y": 2, "z": 3
@{...obj}
@(...obj)

Ниже описана логика работы оператора ... со всеми поддерживаемыми типами данных. Если тип данных не поддерживает оператор ..., возникает исключение.

# Object

В контексте список оператор проверяет, что все ключи объекта являются строковыми представлениями чисел от 1 до N, где N - количество записей в объекте. Затем он создает последовательность:

значение-по-ключу-1, значение-по-ключу-2, ..., значение-по-ключу-N

например,

var obj = @{1: "a", 2: "b"}
@[...obj]                     # создается последовательность "a", "b"

Если ключи объекта не удовлетворяют такому критерию, возникает исключение.

В контексте ключ-значение создается последовательность пар, которая включает в себя все записи объекта в том же порядке, в котором они хранятся в объекте:

var obj = @{1: "a", 2: "b"}
@{...obj}                     # создается последовательность "1": "a", "2": "b"

# Array

В контексте список создается последовательность элементов массива в порядке их следования:

var ary = @[1, 2]
f(...ary)                     # создается последовательность 1, 2

В контексте ключ-значение создается последовательность пар, ключ в которой - порядковый номер элемента (число типа Real), а значение - сам элемент:

var ary = @["a", "b"]
var obj = @{...ary}           # создается последовательность 1: "a", 2: "b"

# HashSet

В контексте список создается последовательность элементов множества в неопределенном порядке:

var s = Set(1, 2)
@[...s]                       # создается последовательность из двух элементов 1 и 2
                              # порядок элементов не определен

В контексте ключ-значение создается последовательность пар, ключи в которой - числа от 1 до N по порядку (N - количество элементов во множестве), а значения - элементы множества в неопределенном порядке.

# HashMap

Контекст список не поддерживается (вызывает исключение).

В контексте ключ-значение создается последовательность, которая содержит все записи коллекции в неопределенном порядке:

var map = @(1: "a", 2: "b")
@{...map}                     # создается последовательность 1: "a", 2: "b"
                              # порядок элементов не определен

# Iterator

В контексте список создается последовательность всех значений итератора, которая получилась бы при проходе по нему в цикле:

var obj = @{1: "a", 2: "b"}
@[...obj.keys()]              # создается последовательность "1", "2"

Контекст ключ-значение не поддерживается (вызывает исключение).

# Оператор classof (получитьКласс)

Оператор classof имеет синтаксис

'classof' <выражение>

Если результатом вычисления выражения является экземпляр класса, определенного с помощью инструкции class, возвращается его класс:

class C {}
var x = new C();

classof x;                    # возвращает класс C

Если результатом вычисления выражения является экземпляр записи, определенной с помощью функции Struct, то возвращается ее формат:

var R = Struct(`R`, `x`);
var rr = R(1);

classof rr;                   # возвращает формат структуры R

Иначе возвращается nil:

classof @[];                  # nil

# Оператор instanceof (экземпляр)

Оператор instanceof имеет синтаксис

<экземпляр> 'instanceof' <класс>

где <экземпляр> и <класс> - произвольные выражения. Оператор возвращает true, если

  • <экземпляр> является экземпляром класса <класс> или другого класса, который унаследован от <класс>

  • <экземпляр> является экземпляром записи, созданной по формату <класс>

и false в противном случае. Если <экземпляр> не является экземпляром класса или записью, либо <класс> не является классом или форматом записи, то возвращается false.

class S {}
class D extends S {}
var R = Struct(`R`, `x`)

var si = new S()
var di = new D()
var rr = R(1)

si instanceof S               # true
di instanceof S               # true

si instanceof D               # false
di instanceof D               # true

rr instanceof R               # true
rr instanceof S               # false

@[] instanceof S              # false
si instanceof @[]             # false

# Оператор new (новый)

Оператор new имеет синтаксис

'new' <класс> '(' [<аргумент-1> [',' <аргумент-2>] ... [',']] ')'

где <класс> - выражение, результатом вычисления которого должен быть класс, <аргумент-1> ... <аргумент-N> - произвольные выражения.

Если выражение <класс> является идентификатором, то класс ищется сначала в пространстве имен классов, а затем в локальной и остальных областях видимости.

Оператор new создает экземпляр класса <класс>, затем вызывает конструктор класса, передавая ему аргументы <аргумент-1> ... <аргумент-N>. Результатом выполнения оператора является созданный экземпляр класса. Если количество аргументов не соответствует сигнатуре конструктора либо результат вычисления выражения <класс> не является классом, возникает исключение.

class C
{
   constructor(x)
   {
      this.x = x
   }
}

var ci = new C("a")           # создает экземпляр класса C, затем вызывает
                              # его конструктор с аргументом "a"; конструктор
                              # присваивает переданное значение полю x;
                              # переменной ci присваивается созданный экземпляр

ci.x                          # "a"

new "a"()                     # исключение, "а" не является классом
new C(1, 2)                   # исключение, конструктор C принимает
                              # только один параметр

# Приоритеты операторов

Чем выше приоритет оператора, тем раньше он выполняется. Бинарные операторы с одинаковым приоритетом выполняются слева-направо. Префиксные операторы с одинаковым приоритетом выполняются справа-налево, а постфиксные - слева-направо. Чтобы изменить очерёдность выполнения операторов, можно использовать круглые скобки:

2 * 2 + 3;        # 7
2 * (2 + 3);      # 10

Ниже приведена таблица приоритетов операторов:

  • 9: () (вызов функции), [] и . (доступ по индексу/ключу), ++ и -- (постфиксные)

  • 8: - (унарный), !, ++ и -- (префиксные), classof, new

  • 7: *, /, %, &, |

  • 6: +, - (бинарные)

  • 5: ==, !=, <, >, <=, >=, instanceof

  • 4: &&

  • 3: ||

  • 2: ?: тернарный оператор

  • 1: =, +=, -=, *=, /=, %=