# Коллекции

# Литералы коллекций

Для коллекций типа "ключ-значение" (Object и HashMap) литералы имеют вид:

'@'<левая-скобка> [<ключ> [':' <значение>] [',' <ключ> ':' [<значение>]] ... [',']] <правая-скобка>

Ключом может быть идентификатор или целое число (которые всегда автоматически приводятся к String), либо произвольное выражение в квадратных скобках. При создании Object из литерала, результат вычисления выражения в квадратных скобках автоматически приводится к String.

Значением может быть произвольное выражение. Если ключом является идентификатор, то значение можно не указывать. Оно неявно будет взято из переменной с этим идентификатором.

Ограничителями для Object являются фигурные скобки { и }, для HashMap - круглые скобки ( и ). Вычисление литералов "ключ-значение" производится слева направо, первым вычисляется ключ. После последней пары перед правой скобкой может находиться запятая.

Примеры:

@{};     # пустой Object
@();     # пустой HashMap

@{ x: 1, };                  # Object, содержит одну запись { "x" -> 1 }
@( x: 1 + 1, y: 2 + 2 );     # HashMap, содержит две записи ( "x" -> 2, "y" -> 4 )


t = 01:02:03;

# Object, содержит одну запись { "01:02:04" -> "time" }
# результат вычисления t + 1 автоматически приводится к строке
@{ [t + 1]: "time" };

# HashMap, содержит одну запись ( 01:02:04 -> "time" )
# результат вычисления t + 1 в HashMap используется как есть, без приведения
@( [t + 1]: "time" )


k1 = "v1"
k2 = "v2"

# Object, содержит две записи { "k1" -> "v1", "k2" -> "v2" }
# в качестве значений использованы значения переменных k1 и k2
@{ k1, k2 };

Литерал массива (Array) имеет вид

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

Выражения внутри литерала вычисляются по порядку слева-направо, и затем в том же порядке добавляются в массив, т.е. первое выражение имеет индекс 1, второе - 2 и т.д. После последнего выражения перед правой скобкой может находиться запятая.

Примеры:

@[];                             # пустой массив
@[ "1", ];                       # один элемент [ 1 -> "1" ]
@[ "1" + "1", "2" + "2" ];       # два элемента [ 1 -> "11", 2 -> "22" ]

Литерал множества (HashSet) имеет вид

'set(' [<выражение> [',' <выражение>] ... [',']] ')'

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

Примеры:

set();                  # пустое множество
set( 1, 2, );           # два элемента: 1 и 2
set( 1, 1, 2, 2 );      # два элемента: 1 и 2

# Object

Набор пар "ключ-значение", где ключом является строка (String), а значением может быть любой тип данных. Ключи являются регистронезависимыми, и упорядочены лексикографически.

Пример:

o = @{x: 1};
o["x"] == o["X"];       # true, поскольку ключи регистронезависимы

# Конструктор

Создавать экземпляры Object можно с помощью литералов:

o = @{x: 1, y: 2};

# Индексация

Получить значение по ключу можно с помощью оператора индексации:

o = @{k: 1};
v = o["k"];             # 1
v = o.k;                # 1

Обращение к несуществующему значению вызывает исключение:

o = 2{x: 1};
v = o["y"];             # исключение

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

o = @{x: 1};
o["x"] = 2;             # значение по ключу "x" заменено на 2
o["y"] = 3;             # добавлена новая запись "y" -> 3

Удалить запись можно с помощью оператора delete

o = @{x: 1};
delete o.x;
v = o.x;                # исключение, нет значения по ключу "x"

При индексации ключ автоматически приводится к строке, если это невозможно возникает исключение.

Если текст ключа совпадает с именем свойства или метода, то при индексации свойства и методы будут иметь приоритет. В этом случае нужно использовать методы get и put:

o = @{"size": 10};
o.size;                 # 1, выполнилось обращение к свойству size
o.get("size");          # 10, вернулось значение ключа "size"

# Методы

# get(key: Any): Any (получить)

Возвращает значение по ключу key. Ключ автоматически приводится к строке, если такое преобразование возможно. Если преобразовать ключ в строку невозможно, либо по заданному ключу нет значения, возникает исключение. Метод не выполняет поиск среди методов и свойств объекта, только среди его ключей. Пример:

o = @{x: 1};
o.get("x") == 1;        # true
s = o.get("size");      # исключение, нет ключа "size"

# put(key: Any, newValue: Any): Empty (добавить)

Если по ключу key уже есть значение, то оно заменяется на newValue, иначе создаётся новая запись с указанным ключом и значением. Ключ автоматически приводится к типу String. Если имя ключа совпадает с именем свойства или метода, то это свойство или метод останутся неизменными, но добавится новая запись. Пример:

o = @{};
o.put(01:02:03, 10);
o.get("01:02:03") == 10;   # true

# has(key: Any): Boolean (содержит)

Возвращает true, если в объекте есть запись с указанным ключом key, и false в противном случае. Ключ автоматически приводится к типу String. Метод не выполняет поиск среди свойств и методов объекта. Пример:

o = @{x: 1};
o.has("x");                # true
o.has("size");             # false

# remove(key: Any): Boolean (убрать)

Удаляет из объекта запись с указанным ключом. Ключ автоматически приводится к типу String. Метод возвращает true, если запись с указанным ключом существовала и была удалена, и false в противном случае. Метод remove не выполняет поиск среди свойств и методов объекта. Пример:

o = @{x: 1};
o.remove("x");             # true, была удалена запись с ключом "x"
o.remove("x");             # false, нет записи с ключом "x"
o.remove("size");          # false, поиск среди свойств не выполняется

# clear(): Empty (очистить)

Удаляет все записи из объекта. Пример:

o = @{x: 1};
o.clear();
o.size;                    # 0, все записи удалены

# keys(): Iterator (ключи)

Возвращает итератор по ключам записей объекта. Ключи упорядочены лексикографически без учёта регистра. Итератор игнорирует методы и свойства объекта. Пример:

o = @{Y: 1, x: 2};
t = @[];

forall (k in o.keys())
   t.pushBack(k);

# t содержит: @["x", "Y"]

# reverseKeys(): Iterator (ключиВОбратномПорядке)

Аналогичен методу keys() с тем отличием, что элементы перебираются в обратном порядке.

# values(): Iterator (значения)

Возвращает итератор по значениям записей объекта. Значения перечисляются в том же порядке, в котором идут ключи объекта. Итератор игнорирует методы и свойства объекта. Пример:

o = @{Y: 1, x: 2};
t = @[];

forall (v in o.values())
   t.pushBack(v);

# t содержит: @[1, 2];

# reverseValues(): Iterator (значенияВОбратномПорядке)

Аналогичен методу values() с тем отличием, что элементы перебираются в обратном порядке.

# iterator(): Iterator (итератор)

Возвращает итератор по записям объекта. Элементы итератора упорядочены так же, как и ключи объекта. Каждый элемент итератора является объектом с записями:

  • "key" ("ключ") - содержит ключ записи

  • "value" ("значение") - содержит значение записи

При итерации игнорируются свойства и методы объекта. Пример:

o = @{x: 1, y: 2};
ks = @[];
vs = @[];

forall (e in o.iterator())
{
   ks.pushBack(e.key)
   vs.pushBack(e.value)
}

# ks содержит: @["x", "y"]
# vs содержит: @[1, 2]

При обходе коллекции в цикле forall метод iterator вызывать не обязательно, можно просто передать сам объект:

forall (e in o)
{
   ...
}

# reverseIterator(): Iterator (обратныйИтератор)

Аналогичен методу iterator() с тем отличием, что элементы перебираются в обратном порядке.

# Свойства

# size: Real (размер)

Возвращает количество записей в объекте. Свойства и методы не учитываются. Пример:

o = @{x: 1, y: 2};
o.size;              # 2

# empty: Boolean (нетЭлементов)

Возвращает true, если свойство size равно 0, и false в противном случае:

@{}.empty;        # true
@{x: 1}.empty;    # false

# HashMap

Набор пар "ключ-значение", в котором и ключ и значение могут иметь любой тип данных. При поиске ключи сравниваются между собой строго (без приведения типов, в отличие от оператора ==). Поэтому в HashMap могут одновременно находиться записи с ключами, например, real(1), int32(1) и string("1"). При этом ключи HashMap никак не упорядочены. Примеры:

m = @()
m[1_i32] = 0
m["1"] = 1

v = m[1_i32];     # 0
v = m["1"];       # 1
v = m[1];         # исключение, нет ключа real(1)

# Индексация

При индексации HashMap ведёт себя так же, как Object, однако никаких преобразований ключа не выполняется.

# Методы

# get(key: Any): Any (получить)

Возвращает значение по ключу key. Метод не выполняет поиск среди методов и свойств HashMap, только среди его ключей. Если ключ отсутствует, возникает исключение. Пример:

o = @(x: 1);
o.get("x") == 1;              # true
s = o.get("size");            # исключение, нет ключа "size"

# put(key: Any, newValue: Any): Empty (добавить)

Если по ключу key уже есть значение, то оно заменяется на newValue, иначе создаётся новая запись с указанным ключом и значением. Если имя ключа совпадает с именем свойства или метода, то это свойство или метод останутся неизменными, но добавится новая запись. Пример:

o = @();
o.put(01:02:03, 10);
o.get(01:02:03) == 10;    # true

# has(key: Any): Boolean (содержит)

Возвращает true, если в HashMap есть запись с указанным ключом key, и false в противном случае. Метод не выполняет поиск среди свойств и методов HashMap. Пример:

o = @(x: 1);
o.has("x");       # true
o.has("size");    # false

# remove(key: Any): Boolean (убрать)

Удаляет из HashMap запись с указанным ключом. Метод возвращает true, если запись с указанным ключом существовала и была удалена, и false в противном случае. Метод remove не выполняет поиск среди свойств и методов HashMap. Пример:

o = @(x: 1);
o.remove("x");       # true, была удалена запись с ключом "x"
o.remove("x");       # false, нет записи с ключом "x"
o.remove("size");    # false, поиск среди свойств не выполняется

# clear(): Empty (очистить)

Удаляет все записи из HashMap. Пример:

o = @(x: 1);
o.clear();
o.size;           # 0, все записи удалены

# keys(): Iterator (ключи)

Возвращает итератор по ключам записей HashMap. Порядок обхода ключей не определён. Итератор игнорирует методы и свойства объекта. Пример:

o = @(x: 1, y: 2);
forall (k in o.keys())     # k будет принимать значения "x", "y" в неопределённом порядке
   ;

# values(): Iterator (значения)

Возвращает итератор по значениям записей HashMap. Порядок обхода значений не определён. Итератор игнорирует методы и свойства HashMap. Пример:

o = @(x: 1, y: 2);
forall (v in o.values())   # v будет принимать значения 1, 2 в неопределённом порядке
   ;

# iterator(): Iterator (итератор)

Возвращает итератор по записям объекта. Порядок обхода записей не определён. Каждый элемент итератора является объектом с записями:

  • "key" ("ключ") - содержит ключ записи

  • "value" ("значение") - содержит значение записи

При итерации игнорируются свойства и методы объекта. Пример:

o = @(x: 1, y: 2);
forall (e in o.iterator())
   ;

# переменная e будет принимать значения
# - @{key: "x", ключ: "x", "value": 1, "значение": 1}
# - @{key: "y", ключ: "y", "value": 2, "значение": 2}
в неопределённом порядке

При обходе коллекции в цикле forall метод iterator вызывать не обязательно, можно просто передать сам объект:

forall (e in o)
{
   ...
}

# Свойства

# size: Real (размер)

Возвращает количество записей в HashMap. Свойства и методы не учитываются. Пример:

o = @(x: 1, y: 2);
o.size;              # 2

# empty: Boolean (нетЭлементов)

Возвращает true, если свойство size равно 0, и false в противном случае:

@().empty;        # true
@(x: 1).empty;    # false

# Array

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

a = @["1", "2", "3"];      # конструирование массива с помощью литерала
v = a[2];                  # "2"

# Индексация

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

a = @["1", "2"];
v = a[1];            # "1"
v = a.1              # "1"
v = a.0              # исключение

Присваивание заменяет элемент по указанному индексу:

a = @["1", "2"];
a[1] = "3";
v = a.1              # "3"

Если при присваивании передать индекс, который превышает размер массива, то массив расширится на нужное количество элементов. При расширении незаполненные ячейки массива получат значение типа Empty, и обращение к ним будут вызывать исключение, пока их не заполнят.

a = @[];
a.3 = "3";     # ячейки 1 и 2 не заполнены, в ячейке 3 находится строка "3"
v = a.1;       # исключение
a.1 = "1";     # теперь в ячейке 1 находится строка "1"
v = a.1;       # "1"

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

Можно удалить элемент массива с помощью оператора delete, при этом индексы следующих за ним элементов уменьшатся на 1 (т.е. будет удалена указанная ячейка массива вместе с её значением):

a = @[1, 2, 3];
delete a[2];      # теперь массив содержит два элемента: @[1, 3]
v = a[2];         # 3

# Методы

# pushBack(value: Any): Empty (push, добавитьВКонец, добавить)

Вставляет значение value в конец массива. Пример:

a = @["1"];
a.pushBack("2");     # а стал @["1", "2"]
a.2;                 # "2"

# pushFront(value: Any): Empty (добавитьВНачало)

Вставляет значение value в начало массива. Индексы всех элементов, которые были до этого в массиве, увеличиваются на 1. Пример:

a = @["2"];
a.pushFront("1");    # а стал @["1", "2"]
a.1;                 # "1"
a.2;                 # "2"

# popBack(): Any (pop, убратьПоследнийЭлемент, вытолкнуть)

Удаляет из массива последний элемент и возвращает его. Если массив пуст, возникает исключение. Пример:

a = @[1, 2, 3];
e = a.popBack();        # а содержит @[1, 2]
e;                      # 3

@[].popBack()           # исключение

# popFront(): Any (убратьПервыйЭлемент)

Удаляет из массива первый элемент и возвращает его. Если массив пуст, возникает исключение. Пример:

a = @[1, 2, 3];
e = a.popFront();       # а содержит @[2, 3]
e;                      # 1

@[].popFront()          # исключение

# insert(index: Any, value: Any): Empty (вставить)

Вставляет значение value на указанный индекс массива, "раздвигая" существующие элементы. Индексы элементов, которые оказались после вставленного элемента, увеличиваются на 1. Если index больше чем размер массива + 1, то массив расширяется до нужного размера. При этом новые ячейки заполняются значением Empty, и обращение к ним будет приводить к исключению, пока их не заполнят. index может быть Number или строкой, которая содержит десятичное число. Если индекс равен нулю или отрицательный, возникает исключение. Пример:

a = @["1", "3"];

a.insert(2, "2");    # a стал @["1", "2", "3"]
a.2;                 # "2"
a.3;                 # "3"

a.insert(5, "5");    # a стал @["1", "2", "3", <Empty>, "5"]
a.5;                 # "5"
v = a.4;             # исключение

# remove(index: Any): Empty (убрать)

Удаляет элемент с указанным индексом. Индексы всех элементов, располагавшихся после удалённого, уменьшаются на 1. Метод возвращает true, если был передан допустимый индекс и было выполнено удаление элемента, и false в противном случае. Индекс может быть Number или строкой, которая содержит десятичное число. Пример:

a = @["1", "2", "3"];
a.remove(2);         # true, был удалён элемент с индексом 2
                     # a стал равен @["1", "3"]
a.remove(3);         # false, в массиве нет элемента с индексом 3

# clear(): Empty (очистить)

Удаляет все элементы из массива. Пример:

a = @[1, 2];
a.clear();
a.size;           # 0

# indexOf(value: Any, offset: Number = 1): Real (номерЭлемента)

Находит в массиве наименьший индекс N, такой что array[N] совпадает с value. Поиск начинается с элемента с номером offset. Если offset меньше 1 или больше размера массива плюс 1, возникает исключение. При поиске элементы сравниваются строго, т.е. приведение типов не выполняется (в отличие от оператора ==). Если не удалось найти ни одного элемента, совпадающего с value, возвращается nil. Примеры:

var a = @[`a`, `b`, `c`];
a.indexOf(`b`);                     # 2
a.indexOf(`d`);                     # nil


# цикл собирает в массив f все индексы элемента `x`
# в массиве а, т.е. f будет равен @[2, 5]

var a = @[`a`, `x`, `b`, `c`, `x`]
var f = @[]
for (var i = a.indexOf(`x`); i; i = a.indexOf(`x`, i + 1))
   f.pushBack(i)


var a = @[1_i32]
a.indexOf(1)                        # nil, сравнение при поиске выполняется строго

# join(separator: String): String (объединить)

Эквивалентен вызову iterator().join(separator). Пример:

@[1, 2, 3].join(`:`)                # "1:2:3"

# iterator(): Iterator (итератор)

Возвращает итератор по всем элементам массива. При итерации игнорируются методы и свойства массива. Элементы перебираются в порядке возрастания индексов. Пример:

a = @[1, 2, 3]
forall (e in a.iterator())             # e последовательно принимает значения 1, 2, 3
   ;

При обходе массива в цикле forall метод iterator вызывать не обязательно, можно просто передать сам объект:

forall (e in a)
   ...

# reverseIterator(): Iterator (обратныйИтератор)

Возвращает итератор по всем элементам массива. При итерации игнорируются методы и свойства массива. Элементы перебираются в порядке убывания индексов. Пример:

a = @[1, 2, 3]
forall (e in a.reverseIterator())      # e последовательно принимает значения 3, 2, 1
   ;

# Свойства

# size: Real (размер)

Возвращает количество элементов в массиве. Пример:

a = @[1, 2];
a.size;           # 2

# empty: Boolean (нетЭлементов)

Возвращает true, если свойство size равно 0, и false в противном случае:

@[].empty;        # true
@[1].empty;       # false

# front: Any (первыйЭлемент)

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

@[1, 2, 3].front;       # 1
@[].front               # исключение

# back: Any (последнийЭлемент)

Возвращает значение последнего элемента массива. Если массив пуст, возникает исключение.

@[1, 2, 3].back;        # 3
@[].back                # исключение

# HashSet

Множество элементов, каждый элемент которого уникален (т.е. встречается только один раз). Элементы множества не упорядочены. При поиске используется строгое сравнение значений без приведения типов (в отличие от оператора ==), поэтому HashSet может одновременно содержать, например, real(1) и int32(1). Не поддерживает оператор индексации. Пример:

s = set(1_i32, 1_i64);        # конструирование HashSet с помощью литерала
s.has(1_i32);                 # true
s.has(1);                     # false

# Методы

# put(value: Any): Empty (добавить)

Добавляет элемент во множество. Если таковой элемент уже есть, то оператор не делает ничего. Пример:

s = set(1, 2);

s.put(3);
s.size;           # 3, добавился элемент 3

s.put(1);
s.size;           # 3, элемент 1 уже содержался во множестве

# has(value: Any): Boolean (содержит)

Возвращает true, если value есть во множестве, и false в противном случае. Пример:

s = set(1, 2);

s.has(2);         # true
s.has(2_i32);     # false

# remove(value: Any): Boolean (убрать)

Удаляет элемент из множества. Возвращает true, если элемент value содержался во множестве и был удалён, и false в противном случае. Пример:

s = set(1, 2);

s.remove(2);         # true, был удалён элемент 2
s.remove(1_i32);     # false, множество не содержит элемента 1_i32

# clear(): Empty (очистить)

Удаляет все элементы из множества. Пример:

s = set(1, 2);
s.clear();
s.size;              # 0

# iterator(): Iterator (итератор)

Возвращает итератор по элементам множества. При итерации игнорируются методы и свойства HashSet. Порядок обхода элементов не определён. Пример:

s = set(1, 2, 3)
forall (e in s.iterator())       # e принимает значения 1, 2, 3 в неопределённом порядке
   ;

При обходе множества в цикле forall метод iterator вызывать не обязательно, можно просто передать сам объект:

forall (e in s)
   ...

# Свойства

# size: Real (размер)

Возвращает количество элементов во множестве. Пример:

s = set(1, 2);
s.size;           # 2

# empty: Boolean (нетЭлементов)

Возвращает true, если свойство size равно 0, и false в противном случае:

set().empty;        # true
set(1).empty;       # false

# Bytes

Изменяемый массив беззнаковых байтов (значения от 0 до 255).

# Конструктор

Экземпляр Bytes можно создать с помощью функции

bytes(...): Bytes

Функцию можно вызвать

  • без аргументов - возвращает пустой массив

  • один аргумент типа String - возвращает массив, который содержит все байты переданной строки

  • один аргумент типа Bytes - возвращает копию этого массива

  • один и более аргументов с типами Real, Int32 или Int64 - возвращает массив, который содержит все аргументы по порядку; если значение аргумента выходит за пределы диапазона 0-255, возникает исключение

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

Bytes()                    # пустой массив
Bytes("abc")               # содержит три байта - 97, 98 и 99
Bytes(1, 2_i32, 3_i64)     # содержит три байта - 1, 2 и 3
Bytes(Money(1))            # исключение

# Индексация

Получить или изменить значение элемента Bytes можно с помощью оператора индексации:

b = Bytes();
b.resize(2);

b[1] = 10;
b[2] = 20;

b[2];                # 20

Присвоение элементу недопустимого значения (вне диапазона 0-255) вызывает исключение.

В отличие от Array, оператор индексации никогда не изменяет размер Bytes, присвоение значения несуществующему элементу вызывает исключение:

b = Bytes();
b[1] = 1;            # исключение

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

b = Bytes(1, 2);
delete b[1];         # исключение

# Методы

# slice(begin: Number, count: [Number] = 0): Bytes (срез)

Возвращает срез массива, который начинается с элемента с индексом begin и содержит count элементов. Если count не задан, то возвращаются все элементы начиная с begin и до конца массива. Пример:

b = Bytes(11, 22, 33, 44);
b.slice(2);                   # Bytes(22, 33, 44)
b.slice(2, 2);                # Bytes(22, 33)
b.slice(4, 0)                 # Bytes()

# resize(newSize: Number): Empty (установитьРазмер)

Изменяет размер массива на newSize. Если newSize больше текущего, в конец массива добавляются нулевые элементы. Если newSize меньше текущего, отбрасывается нужное количество элементов с конца. Пример:

b = Bytes(11, 22, 33, 44)
b.resize(2);            # размер 2, содержит байты 11, 22
b.resize(4);            # размер 4, содержит байты 11, 22, 0, 0

# clear(): Empty (очистить)

Эквивалентен resize(0). Удаляет все элементы и устанавливает размер 0.

# iterator(): Iterator (итератор)

Возвращает итератор по элементам массива. Пример:

bs = Bytes(1, 2, 3);

r = 0;
i = bs.iterator();
while (i.hasNext())
   r += i.next();       # метод next последовательно возвращает 1, 2, 3
r;                      # 6

r = 0;
forall (b in bs)
   r += b;              # b последовательно принимает значения 1, 2, 3
r;                      # 6

# decode(encoding): String (декодироватьИз)

Конвертирует байты из кодировки encoding в UTF-8 и возвращает результат конвертации в виде строки. Если указана несуществующая кодировка, возникает исключение.

Bytes(0xA0, 0xA1, 0xA2).decode(`cp866`)         # "абв"

# Свойства

# size: Real (размер)

Возвращает количество элементов в массиве:

Bytes(11, 22, 33).size;          # 3

# empty: Boolean (нетЭлементов)

Возвращает true, если размер массива равен нулю, и false в противном случае:

Bytes().empty;                   # true
Bytes(1, 2, 3).empty             # false

# Struct

Набор пар ключ-значение (ассоциативный массив), в котором набор ключей фиксирован и заранее известен. Ключами могут быть только строки, значения могут быть произвольными. Этот тип данных также называют Запись (Record). Порядок элементов Struct фиксирован, поэтому к полям можно обращаться не только по имени, но и по номеру, причем доступ по номеру имеет сложность O(1), а доступ по имени поля - O(log2(N)), где N - количество полей. Также Struct занимает меньше памяти по сравнению с Object, поскольку в ней хранятся только значения полей. Сами поля хранятся в формате записи, по которому она была создана. Поля могут иметь значение по умолчанию; если при создании записи такому полю не было присвоено значение, то оно получит значение по умолчанию. Если значение по умолчанию не задано, и полю при инициализации не было присвоено значение, то оно будет инциализировано Empty, обращение к этому полю будет вызывать исключение. Если значение по умолчанию имеет ссылочный тип данных (например, Array или Bytes), то такое поле во всех экземплярах записи будет инициализироваться ссылкой на одно и то же значение (т.е. при инициализации оно не копируется). Поэтому ссылочные типы данных как правило не следует использовать для значений по умолчанию.

Формат записи создается с помощью функции Struct (Структ). Первым аргументом должно быть имя формата (String), остальные аргументы - форматы полей (String или Object) по порядку. Порядок полей записи будет совпадать с порядком аргументов, количество полей должно быть больше или равно 1. Если формат поля является

  • строкой, то именем поля будет эта строка, а значением по умолчанию - Empty

  • объектом с одним ключом, то именем поля будет ключ, а значением по умолчанию - его значение

  • иначе возникает исключение

Функция возвращает созданный формат записи.

var R = Struct(`R`, `x`, @{ y: 0 })

# исключение, формат поля должен быть строкой или объектом
var E = Struct(`E`, 1)

# исключение, формат поля должен объектом с одним ключом
var E = Struct(`E`, @{x: 1, y: 2})

В примере создается формат записи с именем R и двумя полями x и y. Поле x имеет номер 1 и не имеет значения по умолчанию, поле y - номер 2 и значение по умолчанию 0.

По формату можно создать произвольное количество записей. Формат записи является функцией, которая при вызове возвращает новую запись. Аргументы используются для инициализации полей записи: первый аргумент инциализирует первое поле, второй - второе и т. д. Количество аргументов может быть меньше количества полей формата, при этом некоторые поля записи будут инициализированы по умолчанию. Если количество аргументов больше количества полей, возникает исключение.

# создается запись по формату R с начальными значениями полей
# x = 1, y = 2
var rr = R(1, 2)

# создается запись по формату R с начальными значениями полей
# x = 1, y = 0 (по умолчанию)
var rr = R(1)

# создается запись по формату R с начальными значениями полей
# x = Empty, y = 0
var rr = R()

# исключение, неправильное количество значений
var rr = R(1, 2, 3)

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

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

  • если размер массива больше количества полей, то лишние значения игнорируются

Если передан Object, то поля записи инициализируются значениями одноименных полей объекта. Если в объекте отсутствуют значения для каких-либо полей, эти поля будут инициализированы по умолчанию. Лишние (не соответствующие полям записи) поля в объекте игнорируются.

# создается запись по формату R с начальными значениями полей
# x = 1, y = 2
var rr = R.from(@[1, 2])

# создается запись по формату R с начальными значениями полей
# x = 1, y = 0 (по умолчанию)
var rr = R.from(@[1])

# создается запись по формату R с начальными значениями полей
# x = 1, y = 2, лишнее значение 3 в массиве игнорируется
var rr = R.from(@[1, 2, 3])

# создается запись по формату R с начальными значениями полей
# x = 1, y = 2
var rr = R.from(@{x: 1, y: 2})

# создается запись по формату R с начальными значениями полей
# x = 1, y = 0 (по умолчанию)
var rr = R.from(@{x: 1})

# создается запись по формату R с начальными значениями полей
# x = 1, y = 2, лишнее значение z = 3 в объекте игнорируется
var rr = R.from(@{x: 1, y: 2, z: 3})

Обращаться к полям записи можно по имени и по номеру. Если передана строка, она всегда считается именем поля; Real, Int32 и Int64 считаются номером поля. В частности, в выражениях вида s.1 доступ выполняется по имени поля "1", а не по его номеру.

Операторы classof и instanceof поддерживают работу со структурами: classof возвращает формат переданной структуры, а instanceof возвращает true, если структура создана по данному формату.

classof rr                          # возвращает формат структуры R
rr instanceof R                     # возвращает true
rr instanceof Struct(`S`, `x`)      # возвращает false

# Индексация

Struct поддерживает оператор индексации, с помощью него можно считывать и записывать значения полей записи. Если индекс является числом (Real, Int32 и Int64), то выполняется доступ к полю по его номеру, а если строкой - по имени. Если поля с таким номером или именем не существует, возникает исключение.

var R = Struct(`R`, `x`, `y`)
var rr = R(`a`, `b`)

rr.x                                   # доступ по имени поля "x",
                                       # возвращается строка "a"
rr[`x`]                                # аналогично

var x = r.1                            # доступ по имени поля "1";
                                       # исключение, такого поля нет
var x = rr[1]                          # доступ по номера поля 1,
                                       # возвращается строка "а"

# Методы формата записи

# nameOf(index: Number): String (имяКлюча)

Возвращает имя поля по его номеру index. Если поля с таким номером нет, возникает исключение.

var R = Struct(`R`, `x`, `y`)
R.nameOf(2)                         # "y"
R.nameOf(3)                         # исключение

# indexOf(name: String): Real (номерКлюча)

Возвращает имя поля по его имени name. Если поля с таким именем нет, возникает исключение.

var R = Struct(`R`, `x`, `y`)
R.indexOf(`y`)                         # 2
R.indexOf(`z`)                         # исключение

# keys(): Iterator (ключи)

Возвращает итератор по именам полей записи.

var R = Struct(`R`, `x`, `y`)
var a = @[]
forall (k in R.keys())
   a.pushBack(k)

# a содержит строки "x", "y"

# hasDefaultValue(nameOrIndex: Any): Boolean (имеетЗначениеПоУмолчанию)

Возвращает true, если поле с именем или номером nameOrIndex имеет значение по умолчанию, иначе - false. Если такого поля в записи нет, возникает исключение.

var R = Struct(`R`, `x`, @{y: 0})
R.hasDefaultValue(`y`)                 # true
R.hasDefaultValue(2)                   # true
R.hasDefaultValue(`x`)                 # false
R.hasDefaultValue(`z`)                 # исключение

# defaultValueOf(nameOrIndex: Any): Any (значениеПоУмолчанию)

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

var R = Struct(`R`, `x`, @{y: 0})
R.defaultValueOf(`y`)                  # 0
R.defaultValueOf(2)                    # 0
R.defaultValueOf(`x`)                  # исключение
R.defaultValueOf(`z`)                  # исключение

# from(arrayOrObject: Any): Any (из)

Создает экземпляр записи, инциализируя его значениями из массива или объекта arrayOrObject. Подробности механизма инициализации приведены ранее в разделе с общей информацией о Struct.

var R = Struct(`R`, `x`, `y`)
var rr = R.from(@["a", "b"])           # инициализирует поле "x" значением
                                       # "a", поле "y" - "b"
var rr = R.from(@{x: "a", y: "b"})     # аналогично

# Свойства формата записи

# size: Real (размер)

Возвращает количество полей записи:

var R = Struct(`R`, `x`, `y`)
R.size                                 # 2

# name: String (имя)

Возвращает имя формата записи:

var R = Struct(`R`, `x`, `y`)
R.name                                 # "R"

# Методы экземпляра записи

# get(nameOrIndex: Any): Any (получить)

Возвращает значение поля записи с указанным именем или номером nameOrIndex. Если такого поля нет в формате записи или оно не инициализировано, возникает исключение.

var R = Struct(`R`, `x`, `y`)
var rr = R("a")

rr.get(`x`)                            # значение поля x - "a"
rr.get(1)                              # аналогично

var y = rr.get(`y`)                    # исключение, поле y не инициализировано
var z = rr.get(`z`)                    # исключение, такого поля нет
var z = rr.get(3)                      # аналогично

# put(nameOrIndex: Any, value: Any): Empty (добавить)

Записывает значение value в поле записи с именем или номером nameOrIndex. Если такого поля нет в формате записи, возникает исключение.

var R = Struct(`R`, `x`, `y`)
var rr = R("a", "b")

rr.put(`y`, `c`)                       # записывает строку `c` в поле с именем `y`
rr.put(2, `c`)                         # аналогично

rr.put(`z`, `c`)                       # исключение, такого поля нет
rr.put(3, `c`)                         # аналогично

# has(nameOrIndex: Any): Boolean (содержит)

Возвращает true, если в записи есть поле с именем или номером nameOrIndex (даже если оно не инициализировано), иначе - false.

var R = Struct(`R`, `x`, `y`)
var rr = R("a")

rr.has(`y`)                            # true
rr.has(2)                              # аналогично

rr.has(`z`)                            # false
rr.has(3)                              # аналогично

# keys(): Iterator (ключи)

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

var R = Struct(`R`, `x`, `y`)
var rr = R("a", "b")
var a = @[]
forall (k in rr.keys())
   a.pushBack(k)

# a содержит строки "x", "y"

# values(): Iterator (значения)

Возвращает итератор по значениям полей записи, аналогичен одноименному методу формата записи. Значения упорядочены по номерам ключей в порядке возрастания.

var R = Struct(`R`, `x`, `y`)
var rr = R("a", "b")
var a = @[]
forall (v in rr.values())
   a.pushBack(v)

# a содержит строки "a", "b"

# iterator(): Iterator (итератор)

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

  • key (ключ) - имя поля

  • value (значение) - значение поля

Перебор выполняется в порядке возрастания номеров полей. Пример:

var R = Struct(`R`, `x`, `y`)
var rr = R("a", "b")
var ks = @[]
var vs = @[]
forall (e in rr.iterator())
{
   ks.pushBack(e.key)
   vs.pushBack(e.value)
}

# ks содержит строки "x", "y"
# vs содержит строки "a", "b"

При обходе записи в цикле forall метод iterator вызывать не обязательно, можно просто передать сам объект:

forall (e in rr)
   ...

# Свойства экземпляра записи

# size: Real (размер)

Возвращает количество полей записи:

var R = Struct(`R`, `x`, `y`)
var rr = R()
rr.size                                # 2