Skip to main content

EOL

This document was generated using AI assistance

Content may include inaccuracies, outdated information, or technical errors. Users are advised to cross-check critical information before implementation.

Epsilon Object Language is statement-oriented: it has variables, loops, assignment and object creation. It is one of the two languages that can change a model, so it is what a quick fix is written in.

An EOL body in the console, answered with the names it collects

Two shapes of body

EOL bodies come in two shapes, and the shape decides what the body does.

A body with no statement separator is a single expression, evaluated for its value:

self.name <> null

A body containing a ; is a program that runs its statements in order and may change the model:

self.name = self.name.trim();

So a quick fix implementation always ends its statements with ;, and a rule body meant only to check something must not contain one.

Writing an expression

Feature navigation is a dot, and collection operations are called with a dot as well:

self.outgoingRelationships.size() > 0

Strings are single-quoted. Comparison is = and <>; the boolean operators are and, or, not.

EOL is dynamically typed and has no content assist. The body is parsed before it runs, so a syntax error is underlined in the editor, but a misspelt feature name is reported only when the expression is evaluated.

Variables in scope

self is the object the expression is evaluated against, and it is read-only: you can change what it contains, but you cannot rebind the name itself.

The model holding self is bound under the name M, which gives you two further ways in:

  • BusinessProcess.all - every instance of a type in that model.
  • M!BusinessProcess.all - the same, written with the model name explicitly, which you need when a type name is ambiguous.

var declares your own variables inside a program.

print and println write text while the expression runs. In the console that text is shown as its own block above the result.

Examples

Reading, where self is an ArchiMate element:

The element carries a name:

self.name.isDefined() and self.name.trim() <> ''

Every element carries an owner property:

self.properties.exists(p | p.key = 'owner')

The value of that property:

self.properties.select(p | p.key = 'owner').first().value

Every business process in the model, sorted by name:

M!BusinessProcess.all.sortBy(e | e.name)

Elements that no relationship touches:

M!ArchimateElement.all.select(e |
e.incomingRelationships.isEmpty() and e.outgoingRelationships.isEmpty())

Modifying, where self is an ArchiMate element:

Trim a stray space out of a name:

self.name = self.name.trim();

Give the element an owner property:

var p = new M!Property;
p.key = 'owner';
p.value = 'unassigned';
self.properties.add(p);

Fill in a placeholder documentation on every element that has none, and report what was touched:

for (e in M!ArchimateElement.all.select(e | e.documentation.isUndefined())) {
e.documentation = 'To be documented';
println('documented: ' + e.name);
}

Normalize property keys across the model:

for (p in M!Property.all.select(p | p.key <> p.key.toLowerCase())) {
p.key = p.key.toLowerCase();
}

What a modifying body produces is a set of individual changes. Whether they are written straight to the model or held for you to approve one by one is set by the apply mode. See the apply mode.

Further reading

Epsilon Object Language documentation

Language reference

Variables

An EOL body sees four names, and no others.

NameMeaning
selfThe object the expression runs against. Read-only: you can change what it contains, not what the name points at.
MThe model that holds self. It is the only model bound, so M!Type is the only qualified type form.
nullThe absent value. Read-only.
SystemCarries the two print streams, System.out and System.err.

Text written with print, println and System.out is captured and shown with the result. Text written with err, errln and System.err is not.

Comments and annotations

SyntaxMeaning
// textLine comment.
/* text */Block comment.
@name valueSimple annotation on an operation declaration.
$name expressionExecutable annotation: the value is an expression evaluated when the operation runs.
@cachedOn an operation that takes no parameters, the result is computed once and reused.

Literals

SyntaxTypeMeaning
'text', "text"StringEither quote character delimits a string.
\b \t \n \f \r \" \' \\StringEscape sequences inside a string.
42IntegerDecimal integer. 42L is a long.
1.5, 1e3, 1.5f, 1.5dRealA dot, an exponent or an f/d suffix makes the number real.
true, falseBooleanBoolean literals.
nullabsentThe absent value.
`name`identifierAn identifier written in backticks, for names that would otherwise be read as keywords.
Sequence{1, 2, 3}SequenceCollection literal. Any collection type name works in place of Sequence.
Sequence{1..10}SequenceRange literal, both bounds included.
Map{'a' = 1, 'b' = 2}MapMap literal, key = value.
Tuple{name = 'a', size = 2}TupleTuple literal with named fields, read back as t.name.
M!Status#activeenumeration literalThe literal active of the enumeration Status.
#activeenumeration literalThe same, when the label is unambiguous across the model.

Type names

SyntaxMeaning
String, Integer, Real, BooleanThe primitive types.
AnyAny value.
Nothing, NoneThe empty type.
Sequence, Bag, Set, OrderedSet, List, CollectionCollection types. List is a synonym of Sequence.
ConcurrentBag, ConcurrentSetCollection types safe for concurrent modification.
Map, ConcurrentMapMap types.
TupleTuple type.
Sequence(String), Map(String, Integer)A collection or map type with its content types. Sequence<String> is the same thing.
BusinessProcessA metaclass of the model.
M!BusinessProcessThe same metaclass, qualified by the model name. Use this form when a type name is ambiguous.
Native('java.lang.Math')A type resolved by class name from the server classpath.

Operators

Grouped from tightest binding to loosest. Every level is left-associative.

LevelOperatorsMeaning
Navigation.Reads a feature, or calls an operation.
Navigation?.The same, but yields null instead of failing when the left side is null.
Navigation->Accepted as a synonym of ..
Indexingc[i]Item i of a collection, or the value under key i of a map.
Postfixx++, x--Adds or subtracts one and assigns the result back to x.
Unarynot x, -xNegation of a boolean, of a number.
Multiplicative*, /Product, quotient.
Additive+, -Sum, difference. + also concatenates strings.
Relational=, ==Equality. The two spellings behave alike.
Relational<>, !=Inequality.
Relational>, <, >=, <=Ordering comparison.
Relationala ?: ba when a is not null, otherwise b. Also yields b when a is an undeclared name.
Logicaland, or, xor, impliesBoolean operators, all at one precedence level.
Logicalc ? a : b, c ? a else bConditional expression.

Assignment

Assignments are statements and end with ;. The target may be a variable, a feature of an object, an item of a list or a key of a map.

SyntaxMeaning
x = v;Assigns v to x.
x := v;The same.
x += v;x = x + v;
x -= v;x = x - v;
x *= v;x = x * v;
x /= v;x = x / v;
x ?= v;Assigns v only when x is null.
x ::= v;Behaves as x = v;.

Statements and control flow

SyntaxMeaning
expression;Evaluates the expression and discards its value.
{ ... }A block. Wherever a single statement is allowed, a block is too.
if (c) sRuns s when c is true.
if (c) s else tRuns s when c is true, t otherwise.
for (e in c) sRuns s once for each element of c, binding it to e.
for (e : M!Type in c) sThe same, with the iterator typed.
while (c) sRuns s while c stays true.
break;Leaves the innermost loop.
breakAll;Leaves every enclosing loop.
continue;Skips to the next iteration of the innermost loop.
switch (v) { case a: ... default: ... }Runs the first case whose value equals v, otherwise the default block. Cases do not fall through.
return v;Ends the body and yields v as its value.
return;Ends the body with no value.
throw v;Raises v as an error, which surfaces as a message in the editor.
delete v;Removes the object, or every object of the collection, from the model.
transaction { ... }Records the block's changes so abort can undo them.
abort;Undoes the changes made since the enclosing transaction began.

A switch compares with equality:

switch (self.eClass().name) {
case 'BusinessProcess': println('a process');
case 'BusinessActor': println('an actor');
default: println('something else');
}

Declarations

SyntaxMeaning
var x;Declares x in the current scope, with no value.
var x = v;Declares x and assigns v.
var x : String;Declares x with a declared type.
var c : new Sequence;Declares c and instantiates the type.
var e : new M!BusinessProcess;Declares e and creates a model element.
ext x = v;Reuses the variable named x from an enclosing scope if there is one, and declares it otherwise.
operation Type name(p : T) : R { ... }Declares an operation callable as target.name(arg), where target is bound to self inside the body.
operation name(p : T) : R { ... }Declares an operation with no context type, callable as name(arg).
function name(p) { ... }A synonym of operation.

Both the context type and the return type are optional:

operation M!ArchimateElement isNamed() : Boolean {
return self.name.isDefined();
}
return M!ArchimateElement.all.reject(e | e.isNamed());

Operations on any value

Available on every value, including null.

SyntaxReturnsMeaning
x.isDefined()BooleanFalse when x is null or the empty string, true otherwise.
x.isUndefined()BooleanThe negation of isDefined().
x.ifUndefined(v)Anyx when it is defined, v otherwise.
x.isTypeOf(T)BooleanTrue when x has exactly type T.
x.isKindOf(T)BooleanTrue when x has type T or a subtype of it.
x.instanceOf(T)BooleanThe same as isKindOf.
x.hasProperty('name')BooleanTrue when x has a feature with that name.
x.owningModel()modelThe model that owns x, or null.
x.nativeType()classThe underlying implementation class of x.
x.asString()StringThe printed form of x.
x.asInteger()IntegerParses x as an integer.
x.asReal(), x.asDouble(), x.asFloat(), x.asLong()numberParses x as the named number kind.
x.asBoolean()BooleanParses x as a boolean.
x.isInteger()BooleanTrue when x parses as an integer.
x.isReal()BooleanTrue when x parses as a real.
x.asDate('dd/MM/yyyy')DateParses x with the given pattern.
x.asUnicode()StringReads x as a hexadecimal code point and returns that character.
x.format('%.2f')StringFormats x with the given format string.
x.print()AnyWrites x to the output and returns x.
x.println()AnyThe same, followed by a line break.
x.print(prefix, suffix)AnyWrites x framed by the two texts. println takes the same pair.
x.err(), x.errln()AnyWrites x to the error stream, which is not shown with the result.
x.asVar('n')AnyBinds x to a new variable n and returns x.
x.asSequence(), x.asSet(), x.asBag(), x.asOrderedSet()collectionWraps a single value in a collection of that kind.
x.size()Integer1 for any value that is not a collection.

String operations

Every public method of the underlying Java string is callable as well, so trim(), length(), substring(a, b), toLowerCase(), toUpperCase(), startsWith(s), endsWith(s), contains(s), indexOf(s), split(regex), matches(regex) and isEmpty() all work.

SyntaxReturnsMeaning
s.length()IntegerThe number of characters.
s.characterAt(i)StringThe character at index i, as a one-character string.
s.toCharSequence()SequenceThe characters, each as a one-character string.
s.firstToUpperCase(), s.ftuc()StringThe string with its first character upper-cased.
s.firstToLowerCase(), s.ftlc()StringThe string with its first character lower-cased.
s.pad(width, ' ', true)StringPads s to width with the given text, on the right when the last argument is true and on the left otherwise.
s.isSubstringOf(t)BooleanTrue when s occurs inside t.
s.replace(regex, r)StringReplaces every match of the regular expression with r.
s.replaceLiteral(l, r)StringReplaces every occurrence of the literal text l with r.
s.escapeXml()StringEscapes the characters that are special in XML text.
s.toEnum()enumeration literalResolves a label of the form 'M!Status#active' to the literal it names.

Number operations

SyntaxReturnsMeaning
n.abs()numberAbsolute value.
n.floor()IntegerLargest integer not greater than n.
n.ceiling()IntegerSmallest integer not less than n.
n.round()IntegerNearest integer.
n.min(m), n.max(m)numberThe smaller, the larger of the two.
n.pow(m)numbern raised to the power m.
n.ln(), n.log()RealNatural logarithm.
n.log10()RealLogarithm base 10.
n.factorial()IntegerThe factorial of n.
i.mod(m)IntegerThe remainder of i divided by m.
i.to(j)SequenceThe integers from i to j inclusive, counting down when j is smaller.
i.iota(j, step)SequenceThe integers from i to j in increments of step.
i.toBinary(), i.toHex()StringThe base-2, base-16 spelling of i.

Date operations

SyntaxReturnsMeaning
d.getDayOfTheWeek()IntegerThe day of the week of d.
d.getDifferenceInDays(e)IntegerThe number of whole days between d and e.

Dates come from asDate, and every public method of the underlying Java date is callable on them.

Collection operations

Reading a feature off a collection collects it: self.properties.key returns a Sequence of every key. The Java collection methods are callable as well, so add(x), addAll(c), remove(x), removeAll(c), clear() and contains(x) all work, and mutating a feature's collection changes the model.

SyntaxReturnsMeaning
c.size()IntegerThe number of elements.
c.isEmpty(), c.notEmpty()BooleanWhether the collection has no elements, has some.
c.first(), c.second(), c.third(), c.fourth(), c.last()elementThe element at that position, or null when the collection is shorter.
c.first(n)collectionThe first n elements.
c.at(i)elementThe element at index i, counting from zero.
c.indexOf(x)IntegerThe index of x, or -1.
c.random()elementAn arbitrary element.
c.includes(x), c.excludes(x)BooleanWhether x is present, absent.
c.includesAll(d), c.excludesAll(d)BooleanWhether every element of d is present, absent.
c.count(x)IntegerHow many times x occurs.
c.including(x), c.excluding(x)collectionA copy with x added, removed.
c.includingAll(d), c.excludingAll(d)collectionA copy with every element of d added, removed.
c.removeAt(i)elementRemoves the element at index i and returns it.
c.invert()collectionA copy in the opposite order.
c.flatten()collectionA copy with nested collections spliced in.
c.clone()collectionA shallow copy.
c.createCollection()collectionA new empty collection of the same kind.
c.powerset()SetEvery subset of c.
c.sum(), c.product()numberThe sum, the product of the elements.
c.min(), c.max()numberThe smallest, the largest element.
c.min(d), c.max(d)numberThe same, with d returned for an empty collection.
c.concat()StringThe printed elements joined end to end.
c.concat(', ')StringThe same, separated by the given text.
c.selectByType(T)collectionThe elements whose type is exactly T.
c.selectByKind(T)collectionThe elements of type T or a subtype.
c.asSequence(), c.asBag(), c.asSet(), c.asOrderedSet()collectionA copy as that collection kind.
c.asConcurrentBag(), c.asConcurrentSet()collectionA copy safe for concurrent modification.
c.stream(), c.parallelStream()Java streamThe collection as a Java stream.

Operations that take a lambda

The iterator may be typed: select(e : M!BusinessProcess | ...) narrows the loop to elements of that type. => is accepted in place of |.

SyntaxReturnsMeaning
c.select(e | p)collectionThe elements for which p holds.
c.reject(e | p)collectionThe elements for which p does not hold.
c.selectOne(e | p)elementOne element for which p holds, or null.
c.selectFirst(e | p)elementThe same.
c.rejectOne(e | p)collectionA copy with one matching element removed.
c.find(e | p)elementBehaves as selectOne.
c.findOne(e | p)elementBehaves as selectOne.
c.collect(e | x)collectionThe value of x for each element.
c.exists(e | p)BooleanTrue when p holds for at least one element.
c.forAll(e | p)BooleanTrue when p holds for every element.
c.count(e | p)IntegerHow many elements satisfy p.
c.one(e | p)BooleanTrue when exactly one element satisfies p.
c.none(e | p)BooleanTrue when no element satisfies p.
c.nMatch(e | p, n)BooleanTrue when exactly n elements satisfy p.
c.atLeastNMatch(e | p, n)BooleanTrue when at least n elements satisfy p.
c.atMostNMatch(e | p, n)BooleanTrue when at most n elements satisfy p.
c.sortBy(e | x)SequenceThe elements ordered by the value of x.
c.closure(e | x)collectionEvery element reachable by applying x over and over.
c.mapBy(e | k)MapThe elements grouped into sequences under the key k.
c.aggregate(e | k, v, init)MapFolds the elements into a map: for each element k gives the key and v the new value, with total bound to the value so far and init used the first time a key is seen.
x.as('n')AnyBinds x to a variable n and returns x, so a long chain can name an intermediate result.

Grouping and folding:

M!ArchimateElement.all.mapBy(e | e.eClass().name)
M!ArchimateElement.all.aggregate(e | e.eClass().name, total + 1, 0)

Every operation above has a parallel twin - parallelSelect, parallelCollect, parallelForAll and so on - which is accepted and runs the same way.

Map operations

SyntaxReturnsMeaning
m.get(k), m[k]valueThe value under key k, or null.
m.getOrDefault(k, d)valueThe value under k, or d.
m.put(k, v), m[k] = v;valueStores v under k.
m.putIfAbsent(k, v)valueStores v only when k has no value.
m.remove(k)valueRemoves k and returns the value it held.
m.containsKey(k), m.containsValue(v)BooleanWhether the key, the value is present.
m.keySet()SetThe keys.
m.values()collectionThe values.
m.size(), m.isEmpty()Integer, BooleanThe number of entries, whether there are none.
m.clear()-Removes every entry.
m.putAll(n)-Copies every entry of n into m.

Model navigation

SyntaxReturnsMeaning
x.featurevalueThe value of a structural feature.
x?.featurevalueThe same, yielding null when x is null.
c.featureSequenceThe feature read off every element of the collection.
x.eClass()metaclassThe metaclass of x. Its name gives the type name.
x.eContainer()objectThe object that contains x, or null for a root.
x.eContents()collectionThe objects x contains directly.
x.eCrossReferences()collectionThe objects x refers to without containing them.
x.eIsSet(f)BooleanWhether the feature is set.
x.id()StringThe identifier of x within the model.
Type.allcollectionEvery instance of Type or a subtype.
Type.allInstancescollectionThe same.
Type.allOfKindcollectionThe same.
Type.allOfTypecollectionEvery instance whose type is exactly Type.
M!Type.allcollectionAny of the above written with the model name, needed when a type name is ambiguous.
M.allContents()collectionEvery object in the model.
M.getElementById(id)objectThe object with that identifier, or null.
M.getElementId(x)StringThe identifier of x.
M.getTypeNameOf(x)StringThe type name of x.
M.getFullyQualifiedTypeNameOf(x)StringThe type name of x qualified by its package.
M.owns(x)BooleanWhether x belongs to the model.
M.hasType('Name')BooleanWhether the model's metamodel declares that type.

Reaching an object from the objects that point at it goes through the feature the metamodel declares for the reverse direction, such as incomingRelationships on an ArchiMate element. Where the metamodel declares no such feature, select over the candidates instead:

M!ArchimateRelationship.all.select(r | r.target = self)

Changing the model

SyntaxMeaning
x.feature = v;Sets a feature to a value.
x.feature.add(y);Adds to a many-valued feature. Adding to a containment feature moves y under x.
x.feature.remove(y);Removes from a many-valued feature.
new M!TypeCreates an element and puts it at the top level of the model until it is placed into a containment feature.
M!Type.createInstance()The same.
var x : new M!Type;Declares a variable and creates the element in one statement.
delete x;Removes x and everything it contains from the model, and clears the references that pointed at it.
M.deleteElement(x)The same for one object.

Create an element, name it and file it under a folder:

var process = new M!BusinessProcess;
process.name = 'Handle claim';
self.elements.add(process);

Assertions

Assertions are on. A failing one ends the body and reports its message.

SyntaxMeaning
assert(c);Fails unless c is true.
assertTrue(c);, assertTrue('message', c);Fails unless c is true.
assertFalse(c);, assertFalse('message', c);Fails unless c is false.
assertEquals(a, b);, assertEquals('message', a, b);Fails unless the two values are equal.
assertEquals(a, b, ulps);Compares two numbers within a tolerance.
assertNotEquals(a, b);Fails when the two values are equal.
assertError(e);Fails unless evaluating e raises an error.
fail('message');Fails outright.

Outside the language

Some of what EOL supports elsewhere has nothing to act on here.

Exactly one model is bound, under the name M, so a model declaration in the body has no effect and there is no second model name to write. A body is not stored in a file, so import finds nothing to load. There is no content assist and no static type checking, so a misspelt feature name parses cleanly and is reported when the expression runs.