EOL
Текст может содержать неточности, устаревшие данные или технические ошибки. Пожалуйста, проверяйте критически важную информацию перед использованием.
Epsilon Object Language ориентирован на операторы: в нём есть переменные, циклы, присваивание и создание объектов. Это один из двух языков, способных изменять модель, поэтому именно на нём пишется быстрое исправление.

Две формы тела
Тела на EOL бывают двух форм, и форма определяет, что тело делает.
Тело без разделителя операторов — это одиночное выражение, вычисляемое ради значения:
self.name <> null
Тело, содержащее ;, — это программа, которая выполняет свои операторы по
порядку и может изменить модель:
self.name = self.name.trim();
Поэтому реализация быстрого исправления всегда завершает свои операторы символом
;, а тело правила, предназначенное только для проверки, не должно его
содержать.
Как пишется выражение
Характеристики читаются через точку, операции над коллекциями вызываются так же:
self.outgoingRelationships.size() > 0
Строки заключаются в одинарные кавычки. Сравнение — = и <>; логические
операторы — and, or, not.
Типизация в EOL динамическая, автодополнения нет. Тело разбирается до запуска, поэтому синтаксическую ошибку редактор подчёркивает сразу, а опечатку в имени характеристики вы увидите только при вычислении выражения.
Видимые переменные
self — объект, на котором вычисляется выражение, и он доступен только для
чтения: вы можете изменить его содержимое, но не можете переназначить само имя.
Модель, которой принадлежит self, доступна под именем M. Отсюда ещё два
способа обратиться к типу:
BusinessProcess.all— все экземпляры типа в этой модели.M!BusinessProcess.all— то же самое с явным указанием имени модели, что нужно, когда имя типа неоднозначно.
var объявляет собственные переменные внутри программы.
print и println выводят текст во время выполнения выражения. В консоли этот
текст показывается отдельным блоком над результатом.
Примеры
Чтение, где self — элемент ArchiMate:
У элемента есть имя:
self.name.isDefined() and self.name.trim() <> ''
У каждого элемента есть свойство owner:
self.properties.exists(p | p.key = 'owner')
Значение этого свойства:
self.properties.select(p | p.key = 'owner').first().value
Все бизнес-процессы модели, отсортированные по имени:
M!BusinessProcess.all.sortBy(e | e.name)
Элементы, которых не касается ни одна связь:
M!ArchimateElement.all.select(e |
e.incomingRelationships.isEmpty() and e.outgoingRelationships.isEmpty())
Изменение, где self — элемент ArchiMate:
Убрать лишний пробел из имени:
self.name = self.name.trim();
Добавить элементу свойство owner:
var p = new M!Property;
p.key = 'owner';
p.value = 'unassigned';
self.properties.add(p);
Заполнить документацию-заглушку у каждого элемента, где её нет, и сообщить, что было затронуто:
for (e in M!ArchimateElement.all.select(e | e.documentation.isUndefined())) {
e.documentation = 'To be documented';
println('documented: ' + e.name);
}
Привести ключи свойств к единому виду по всей модели:
for (p in M!Property.all.select(p | p.key <> p.key.toLowerCase())) {
p.key = p.key.toLowerCase();
}
Изменяющее тело порождает набор отдельных изменений. Режим применения задаёт, записать их в модель сразу или придержать, чтобы вы утвердили каждое по отдельности. См. Консоль выражений.
Дополнительные материалы
Документация Epsilon Object Language
Справочник по языку
Переменные
Тело на EOL видит четыре имени и никаких других.
| Имя | Значение |
|---|---|
self | Объект, на котором выполняется выражение. Только для чтения: вы можете изменить его содержимое, но не то, на что указывает имя. |
M | Модель, содержащая self. Это единственная связанная модель, поэтому M!Type — единственная возможная квалифицированная форма типа. |
null | Отсутствующее значение. Только для чтения. |
System | Содержит два потока вывода — System.out и System.err. |
Текст, выведенный через print, println и System.out, перехватывается и
показывается вместе с результатом. Текст, выведенный через err, errln и
System.err, — нет.
Комментарии и аннотации
| Синтаксис | Значение |
|---|---|
// text | Однострочный комментарий. |
/* text */ | Блочный комментарий. |
@name value | Простая аннотация на объявлении операции. |
$name expression | Исполняемая аннотация: значением служит выражение, вычисляемое при выполнении операции. |
@cached | На операции без параметров: результат вычисляется один раз и переиспользуется. |
Литералы
| Синтаксис | Тип | Значение |
|---|---|---|
'text', "text" | String | Строку ограничивает любая из двух кавычек. |
\b \t \n \f \r \" \' \\ | String | Escape-последовательности внутри строки. |
42 | Integer | Десятичное целое. 42L — long. |
1.5, 1e3, 1.5f, 1.5d | Real | Точка, экспонента или суффикс f/d делают число вещественным. |
true, false | Boolean | Логические литералы. |
null | отсутствующее значение | Отсутствующее значение. |
`name` | идентификатор | Идентификатор в обратных кавычках — для имён, которые иначе были бы прочитаны как ключевые слова. |
Sequence{1, 2, 3} | Sequence | Литерал коллекции. Вместо Sequence подходит имя любого типа коллекции. |
Sequence{1..10} | Sequence | Литерал диапазона, обе границы включаются. |
Map{'a' = 1, 'b' = 2} | Map | Литерал отображения, ключ = значение. |
Tuple{name = 'a', size = 2} | Tuple | Литерал кортежа с именованными полями, читаемыми как t.name. |
M!Status#active | литерал перечисления | Литерал active перечисления Status. |
#active | литерал перечисления | То же самое, когда метка однозначна в пределах модели. |
Имена типов
| Синтаксис | Значение |
|---|---|
String, Integer, Real, Boolean | Примитивные типы. |
Any | Любое значение. |
Nothing, None | Пустой тип. |
Sequence, Bag, Set, OrderedSet, List, Collection | Типы коллекций. List — синоним Sequence. |
ConcurrentBag, ConcurrentSet | Типы коллекций, безопасные при конкурентном изменении. |
Map, ConcurrentMap | Типы отображений. |
Tuple | Тип кортежа. |
Sequence(String), Map(String, Integer) | Тип коллекции или отображения с типами содержимого. Sequence<String> — то же самое. |
BusinessProcess | Метакласс модели. |
M!BusinessProcess | Тот же метакласс с указанием имени модели. Эта форма нужна, когда имя типа неоднозначно. |
Native('java.lang.Math') | Тип, разрешаемый по имени класса из classpath сервера. |
Операторы
Сгруппированы от самого сильного связывания к самому слабому. Все уровни левоассоциативны.
| Уровень | Операторы | Значение |
|---|---|---|
| Навигация | . | Читает характеристику или вызывает операцию. |
| Навигация | ?. | То же самое, но даёт null вместо ошибки, когда левая часть равна null. |
| Навигация | -> | Принимается как синоним .. |
| Индексирование | c[i] | Элемент i коллекции или значение по ключу i отображения. |
| Постфиксные | x++, x-- | Прибавляет или вычитает единицу, записывая результат в x. |
| Унарные | not x, -x | Отрицание логического значения, числа. |
| Мультипликативные | *, / | Произведение, частное. |
| Аддитивные | +, - | Сумма, разность. + также склеивает строки. |
| Сравнения | =, == | Равенство. Обе записи ведут себя одинаково. |
| Сравнения | <>, != | Неравенство. |
| Сравнения | >, <, >=, <= | Сравнение по порядку. |
| Сравнения | a ?: b | a, когда a не null, иначе b. Даёт b и тогда, когда a — необъявленное имя. |
| Логические | and, or, xor, implies | Логические операторы, все на одном уровне приоритета. |
| Логические | c ? a : b, c ? a else b | Условное выражение. |
Присваивание
Присваивание — оператор, оно завершается ;. Присвоить можно переменной,
характеристике объекта, элементу списка или ключу отображения.
| Синтаксис | Значение |
|---|---|
x = v; | Присваивает v переменной x. |
x := v; | То же самое. |
x += v; | x = x + v; |
x -= v; | x = x - v; |
x *= v; | x = x * v; |
x /= v; | x = x / v; |
x ?= v; | Присваивает v, только когда x равно null. |
x ::= v; | Ведёт себя как x = v;. |
Операторы и поток управления
| Синтаксис | Значение |
|---|---|
expression; | Вычисляет выражение и отбрасывает его значение. |
{ ... } | Блок. Везде, где допустим одиночный оператор, допустим и блок. |
if (c) s | Выполняет s, когда c истинно. |
if (c) s else t | Выполняет s, когда c истинно, иначе t. |
for (e in c) s | Выполняет s по разу для каждого элемента c, связывая его с e. |
for (e : M!Type in c) s | То же самое с типизированной переменной цикла. |
while (c) s | Выполняет s, пока c остаётся истинным. |
break; | Выходит из ближайшего цикла. |
breakAll; | Выходит из всех объемлющих циклов. |
continue; | Переходит к следующей итерации ближайшего цикла. |
switch (v) { case a: ... default: ... } | Выполняет первый case, значение которого равно v, иначе блок default. Проваливания между ветвями нет. |
return v; | Завершает тело и даёт v как его значение. |
return; | Завершает тело без значения. |
throw v; | Возбуждает v как ошибку; редактор покажет её сообщением. |
delete v; | Удаляет объект или все объекты коллекции из модели. |
transaction { ... } | Записывает изменения блока, чтобы abort мог их отменить. |
abort; | Отменяет изменения, сделанные с начала объемлющей transaction. |
switch сравнивает на равенство:
switch (self.eClass().name) {
case 'BusinessProcess': println('a process');
case 'BusinessActor': println('an actor');
default: println('something else');
}
Объявления
| Синтаксис | Значение |
|---|---|
var x; | Объявляет x в текущей области видимости без значения. |
var x = v; | Объявляет x и присваивает v. |
var x : String; | Объявляет x с указанным типом. |
var c : new Sequence; | Объявляет c и создаёт экземпляр типа. |
var e : new M!BusinessProcess; | Объявляет e и создаёт элемент модели. |
ext x = v; | Берёт x из объемлющей области видимости, а если её там нет — объявляет. |
operation Type name(p : T) : R { ... } | Объявляет операцию, вызываемую как target.name(arg), где target связывается с self внутри тела. |
operation name(p : T) : R { ... } | Объявляет операцию без контекстного типа, вызываемую как name(arg). |
function name(p) { ... } | Синоним operation. |
И контекстный тип, и тип результата необязательны:
operation M!ArchimateElement isNamed() : Boolean {
return self.name.isDefined();
}
return M!ArchimateElement.all.reject(e | e.isNamed());
Операции над любым значением
Доступны для любого значения, включая null.
| Синтаксис | Результат | Значение |
|---|---|---|
x.isDefined() | Boolean | Ложь, когда x равно null или пустой строке, иначе истина. |
x.isUndefined() | Boolean | Отрицание isDefined(). |
x.ifUndefined(v) | Any | x, когда оно определено, иначе v. |
x.isTypeOf(T) | Boolean | Истина, когда x имеет ровно тип T. |
x.isKindOf(T) | Boolean | Истина, когда x имеет тип T или его подтип. |
x.instanceOf(T) | Boolean | То же, что isKindOf. |
x.hasProperty('name') | Boolean | Истина, когда у x есть характеристика с таким именем. |
x.owningModel() | модель | Модель, которой принадлежит x, или null. |
x.nativeType() | класс | Класс реализации, лежащий в основе x. |
x.asString() | String | Печатная форма x. |
x.asInteger() | Integer | Разбирает x как целое. |
x.asReal(), x.asDouble(), x.asFloat(), x.asLong() | число | Разбирает x как число соответствующего вида. |
x.asBoolean() | Boolean | Разбирает x как логическое значение. |
x.isInteger() | Boolean | Истина, когда x разбирается как целое. |
x.isReal() | Boolean | Истина, когда x разбирается как вещественное. |
x.asDate('dd/MM/yyyy') | Date | Разбирает x по заданному шаблону. |
x.asUnicode() | String | Символ с шестнадцатеричной кодовой точкой x. |
x.format('%.2f') | String | Форматирует x по заданной строке формата. |
x.print() | Any | Выводит x и возвращает x. |
x.println() | Any | То же самое с переводом строки в конце. |
x.print(prefix, suffix) | Any | Выводит x в обрамлении двух текстов. println принимает такую же пару. |
x.err(), x.errln() | Any | Выводит x в поток ошибок, который не показывается вместе с результатом. |
x.asVar('n') | Any | Связывает x с новой переменной n и возвращает x. |
x.asSequence(), x.asSet(), x.asBag(), x.asOrderedSet() | коллекция | Оборачивает одиночное значение в коллекцию соответствующего вида. |
x.size() | Integer | 1 для всякого значения, кроме коллекции. |
Операции над строками
На строке доступен и любой публичный метод Java-строки, поэтому trim(),
length(), substring(a, b), toLowerCase(), toUpperCase(),
startsWith(s), endsWith(s), contains(s), indexOf(s), split(regex),
matches(regex) и isEmpty() работают.
| Синтаксис | Результат | Значение |
|---|---|---|
s.length() | Integer | Количество символов. |
s.characterAt(i) | String | Символ по индексу i в виде строки из одного символа. |
s.toCharSequence() | Sequence | Символы, каждый в виде строки из одного символа. |
s.firstToUpperCase(), s.ftuc() | String | Копия s с первым символом в верхнем регистре. |
s.firstToLowerCase(), s.ftlc() | String | Копия s с первым символом в нижнем регистре. |
s.pad(width, ' ', true) | String | Дополняет s до ширины width заданным текстом — справа, когда последний аргумент истинен, иначе слева. |
s.isSubstringOf(t) | Boolean | Истина, когда s встречается внутри t. |
s.replace(regex, r) | String | Заменяет каждое совпадение регулярного выражения на r. |
s.replaceLiteral(l, r) | String | Заменяет каждое вхождение буквального текста l на r. |
s.escapeXml() | String | Экранирует символы, специальные для текста в XML. |
s.toEnum() | литерал перечисления | Разрешает метку вида 'M!Status#active' в литерал, который она называет. |
Операции над числами
| Синтаксис | Результат | Значение |
|---|---|---|
n.abs() | число | Абсолютное значение. |
n.floor() | Integer | Наибольшее целое, не превосходящее n. |
n.ceiling() | Integer | Наименьшее целое, не меньшее n. |
n.round() | Integer | Ближайшее целое. |
n.min(m), n.max(m) | число | Меньшее, большее из двух. |
n.pow(m) | число | n в степени m. |
n.ln(), n.log() | Real | Натуральный логарифм. |
n.log10() | Real | Логарифм по основанию 10. |
n.factorial() | Integer | Факториал n. |
i.mod(m) | Integer | Остаток от деления i на m. |
i.to(j) | Sequence | Целые от i до j включительно, с убыванием, когда j меньше. |
i.iota(j, step) | Sequence | Целые от i до j с шагом step. |
i.toBinary(), i.toHex() | String | Запись i в системе с основанием 2, 16. |
Операции над датами
| Синтаксис | Результат | Значение |
|---|---|---|
d.getDayOfTheWeek() | Integer | День недели для d. |
d.getDifferenceInDays(e) | Integer | Число полных дней между d и e. |
Дату даёт asDate; на ней доступен любой публичный метод Java-даты.
Операции над коллекциями
Чтение характеристики у коллекции собирает её значения: self.properties.key
даёт Sequence всех ключей. Методы Java-коллекций тоже доступны, поэтому
add(x), addAll(c), remove(x), removeAll(c), clear() и contains(x)
работают; изменив коллекцию характеристики, вы измените модель.
| Синтаксис | Результат | Значение |
|---|---|---|
c.size() | Integer | Количество элементов. |
c.isEmpty(), c.notEmpty() | Boolean | Пуста ли коллекция, есть ли в ней элементы. |
c.first(), c.second(), c.third(), c.fourth(), c.last() | элемент | Элемент на этой позиции или null, когда коллекция короче. |
c.first(n) | коллекция | Первые n элементов. |
c.at(i) | элемент | Элемент по индексу i, отсчёт с нуля. |
c.indexOf(x) | Integer | Индекс x или -1. |
c.random() | элемент | Произвольный элемент. |
c.includes(x), c.excludes(x) | Boolean | Присутствует ли x, отсутствует ли. |
c.includesAll(d), c.excludesAll(d) | Boolean | Присутствует ли каждый элемент d, отсутствует ли. |
c.count(x) | Integer | Сколько раз встречается x. |
c.including(x), c.excluding(x) | коллекция | Копия с добавленным, удалённым x. |
c.includingAll(d), c.excludingAll(d) | коллекция | Копия с добавленными, удалёнными элементами d. |
c.removeAt(i) | элемент | Удаляет элемент по индексу i и возвращает его. |
c.invert() | коллекция | Копия в обратном порядке. |
c.flatten() | коллекция | Копия, в которую вложенные коллекции вставлены поэлементно. |
c.clone() | коллекция | Поверхностная копия. |
c.createCollection() | коллекция | Новая пустая коллекция того же вида. |
c.powerset() | Set | Все подмножества c. |
c.sum(), c.product() | число | Сумма, произведение элементов. |
c.min(), c.max() | число | Наименьший, наибольший элемент. |
c.min(d), c.max(d) | число | То же самое, но для пустой коллекции возвращается d. |
c.concat() | String | Напечатанные элементы, соединённые подряд. |
c.concat(', ') | String | То же самое с заданным разделителем. |
c.selectByType(T) | коллекция | Элементы, тип которых в точности T. |
c.selectByKind(T) | коллекция | Элементы типа T или его подтипа. |
c.asSequence(), c.asBag(), c.asSet(), c.asOrderedSet() | коллекция | Копия в виде коллекции этого вида. |
c.asConcurrentBag(), c.asConcurrentSet() | коллекция | Копия, безопасная при конкурентном изменении. |
c.stream(), c.parallelStream() | Java-поток | Коллекция в виде Java-потока. |
Операции, принимающие лямбду
Переменную цикла можно типизировать: select(e : M!BusinessProcess | ...)
сужает перебор до элементов этого типа. Вместо | принимается =>.
| Синтаксис | Результат | Значение |
|---|---|---|
c.select(e | p) | коллекция | Элементы, для которых p выполняется. |
c.reject(e | p) | коллекция | Элементы, для которых p не выполняется. |
c.selectOne(e | p) | элемент | Один элемент, для которого p выполняется, или null. |
c.selectFirst(e | p) | элемент | То же самое. |
c.rejectOne(e | p) | коллекция | Копия с одним удалённым подходящим элементом. |
c.find(e | p) | элемент | Ведёт себя как selectOne. |
c.findOne(e | p) | элемент | Ведёт себя как selectOne. |
c.collect(e | x) | коллекция | Значение x для каждого элемента. |
c.exists(e | p) | Boolean | Истина, когда p выполняется хотя бы для одного элемента. |
c.forAll(e | p) | Boolean | Истина, когда p выполняется для каждого элемента. |
c.count(e | p) | Integer | Сколько элементов удовлетворяет p. |
c.one(e | p) | Boolean | Истина, когда p удовлетворяет ровно один элемент. |
c.none(e | p) | Boolean | Истина, когда p не удовлетворяет ни один элемент. |
c.nMatch(e | p, n) | Boolean | Истина, когда p удовлетворяет ровно n элементов. |
c.atLeastNMatch(e | p, n) | Boolean | Истина, когда p удовлетворяет не менее n элементов. |
c.atMostNMatch(e | p, n) | Boolean | Истина, когда p удовлетворяет не более n элементов. |
c.sortBy(e | x) | Sequence | Элементы, упорядоченные по значению x. |
c.closure(e | x) | коллекция | Все элементы, достижимые повторным применением x. |
c.mapBy(e | k) | Map | Элементы, сгруппированные в последовательности по ключу k. |
c.aggregate(e | k, v, init) | Map | Свёртка элементов в отображение: для каждого элемента k даёт ключ, а v — новое значение; total связывается с накопленным значением, а init подставляется при первой встрече ключа. |
x.as('n') | Any | Связывает x с переменной n и возвращает x. |
Группировка и свёртка:
M!ArchimateElement.all.mapBy(e | e.eClass().name)
M!ArchimateElement.all.aggregate(e | e.eClass().name, total + 1, 0)
У каждой операции выше есть parallel-двойник с тем же поведением:
parallelSelect, parallelCollect, parallelForAll и так далее.
Операции над отображениями
| Синтаксис | Результат | Значение |
|---|---|---|
m.get(k), m[k] | значение | Значение по ключу k или null. |
m.getOrDefault(k, d) | значение | Значение по ключу k или d. |
m.put(k, v), m[k] = v; | значение | Сохраняет v по ключу k. |
m.putIfAbsent(k, v) | значение | Сохраняет v, только когда по k значения нет. |
m.remove(k) | значение | Удаляет k и возвращает хранившееся значение. |
m.containsKey(k), m.containsValue(v) | Boolean | Присутствует ли ключ, значение. |
m.keySet() | Set | Ключи. |
m.values() | коллекция | Значения. |
m.size(), m.isEmpty() | Integer, Boolean | Количество записей, пусто ли отображение. |
m.clear() | - | Удаляет все записи. |
m.putAll(n) | - | Копирует все записи n в m. |
Навигация по модели
| Синтаксис | Результат | Значение |
|---|---|---|
x.feature | значение | Значение структурной характеристики. |
x?.feature | значение | То же самое, но даёт null, когда x равно null. |
c.feature | Sequence | Характеристика, прочитанная у каждого элемента коллекции. |
x.eClass() | метакласс | Метакласс x. Его name даёт имя типа. |
x.eContainer() | объект | Объект, содержащий x, или null для корня. |
x.eContents() | коллекция | Объекты, непосредственно содержащиеся в x. |
x.eCrossReferences() | коллекция | Объекты, на которые x ссылается, не содержа их. |
x.eIsSet(f) | Boolean | Установлена ли характеристика. |
x.id() | String | Идентификатор x внутри модели. |
Type.all | коллекция | Все экземпляры Type или его подтипа. |
Type.allInstances | коллекция | То же самое. |
Type.allOfKind | коллекция | То же самое. |
Type.allOfType | коллекция | Все экземпляры, тип которых в точности Type. |
M!Type.all | коллекция | Любая из форм выше с указанием имени модели — нужна, когда имя типа неоднозначно. |
M.allContents() | коллекция | Все объекты модели. |
M.getElementById(id) | объект | Объект с этим идентификатором или null. |
M.getElementId(x) | String | Идентификатор x. |
M.getTypeNameOf(x) | String | Имя типа x. |
M.getFullyQualifiedTypeNameOf(x) | String | Имя типа x, квалифицированное его пакетом. |
M.owns(x) | Boolean | Принадлежит ли x модели. |
M.hasType('Name') | Boolean | Объявляет ли метамодель этой модели такой тип. |
Чтобы перейти от объекта к тем, кто на него ссылается, используйте обратную
характеристику метамодели — например, incomingRelationships у элемента
ArchiMate. Если метамодель её не объявляет, отберите нужное среди кандидатов:
M!ArchimateRelationship.all.select(r | r.target = self)
Изменение модели
| Синтаксис | Значение |
|---|---|
x.feature = v; | Устанавливает характеристике значение. |
x.feature.add(y); | Добавляет во множественную характеристику. Добавление в характеристику-контейнер перемещает y под x. |
x.feature.remove(y); | Удаляет из множественной характеристики. |
new M!Type | Создаёт элемент и держит его на верхнем уровне модели, пока его не поместят в характеристику-контейнер. |
M!Type.createInstance() | То же самое. |
var x : new M!Type; | Объявляет переменную и создаёт элемент одним оператором. |
delete x; | Удаляет из модели x со всем его содержимым и очищает ссылки на него. |
M.deleteElement(x) | То же самое для одного объекта. |
Создать элемент, дать ему имя и поместить в папку:
var process = new M!BusinessProcess;
process.name = 'Handle claim';
self.elements.add(process);
Проверки
Проверки включены. Непройденная проверка прерывает тело и выводит своё сообщение.
| Синтаксис | Значение |
|---|---|
assert(c); | Не проходит, если c не истинно. |
assertTrue(c);, assertTrue('message', c); | Не проходит, если c не истинно. |
assertFalse(c);, assertFalse('message', c); | Не проходит, если c не ложно. |
assertEquals(a, b);, assertEquals('message', a, b); | Не проходит, если два значения не равны. |
assertEquals(a, b, ulps); | Сравнивает два числа с допуском. |
assertNotEquals(a, b); | Не проходит, если два значения равны. |
assertError(e); | Не проходит, если вычисление e не вызывает ошибку. |
fail('message'); | Не проходит безусловно. |
За пределами языка
Часть возможностей EOL здесь не находит применения.
Связана ровно одна модель, под именем M, поэтому объявление model в теле
ничего не даёт, а второго имени модели нет. Тело не хранится в файле, поэтому
import ничего не находит. Автодополнения и статической проверки типов нет,
поэтому опечатка в имени характеристики успешно разбирается и обнаруживается
только при выполнении выражения.