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

EOL

Этот документ был подготовлен при помощи ИИ

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

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

Тело на EOL в консоли и ответ на него: собранные имена

Две формы тела

Тела на 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 \" \' \\StringEscape-последовательности внутри строки.
42IntegerДесятичное целое. 42L — long.
1.5, 1e3, 1.5f, 1.5dRealТочка, экспонента или суффикс f/d делают число вещественным.
true, falseBooleanЛогические литералы.
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 ?: ba, когда 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)Anyx, когда оно определено, иначе 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()Integer1 для всякого значения, кроме коллекции.

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

На строке доступен и любой публичный метод 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.featureSequenceХарактеристика, прочитанная у каждого элемента коллекции.
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 ничего не находит. Автодополнения и статической проверки типов нет, поэтому опечатка в имени характеристики успешно разбирается и обнаруживается только при выполнении выражения.