EOL
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.

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.
| Name | Meaning |
|---|---|
self | The object the expression runs against. Read-only: you can change what it contains, not what the name points at. |
M | The model that holds self. It is the only model bound, so M!Type is the only qualified type form. |
null | The absent value. Read-only. |
System | Carries 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
| Syntax | Meaning |
|---|---|
// text | Line comment. |
/* text */ | Block comment. |
@name value | Simple annotation on an operation declaration. |
$name expression | Executable annotation: the value is an expression evaluated when the operation runs. |
@cached | On an operation that takes no parameters, the result is computed once and reused. |
Literals
| Syntax | Type | Meaning |
|---|---|---|
'text', "text" | String | Either quote character delimits a string. |
\b \t \n \f \r \" \' \\ | String | Escape sequences inside a string. |
42 | Integer | Decimal integer. 42L is a long. |
1.5, 1e3, 1.5f, 1.5d | Real | A dot, an exponent or an f/d suffix makes the number real. |
true, false | Boolean | Boolean literals. |
null | absent | The absent value. |
`name` | identifier | An identifier written in backticks, for names that would otherwise be read as keywords. |
Sequence{1, 2, 3} | Sequence | Collection literal. Any collection type name works in place of Sequence. |
Sequence{1..10} | Sequence | Range literal, both bounds included. |
Map{'a' = 1, 'b' = 2} | Map | Map literal, key = value. |
Tuple{name = 'a', size = 2} | Tuple | Tuple literal with named fields, read back as t.name. |
M!Status#active | enumeration literal | The literal active of the enumeration Status. |
#active | enumeration literal | The same, when the label is unambiguous across the model. |
Type names
| Syntax | Meaning |
|---|---|
String, Integer, Real, Boolean | The primitive types. |
Any | Any value. |
Nothing, None | The empty type. |
Sequence, Bag, Set, OrderedSet, List, Collection | Collection types. List is a synonym of Sequence. |
ConcurrentBag, ConcurrentSet | Collection types safe for concurrent modification. |
Map, ConcurrentMap | Map types. |
Tuple | Tuple type. |
Sequence(String), Map(String, Integer) | A collection or map type with its content types. Sequence<String> is the same thing. |
BusinessProcess | A metaclass of the model. |
M!BusinessProcess | The 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.
| Level | Operators | Meaning |
|---|---|---|
| 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 .. |
| Indexing | c[i] | Item i of a collection, or the value under key i of a map. |
| Postfix | x++, x-- | Adds or subtracts one and assigns the result back to x. |
| Unary | not x, -x | Negation 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. |
| Relational | a ?: b | a when a is not null, otherwise b. Also yields b when a is an undeclared name. |
| Logical | and, or, xor, implies | Boolean operators, all at one precedence level. |
| Logical | c ? a : b, c ? a else b | Conditional 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.
| Syntax | Meaning |
|---|---|
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
| Syntax | Meaning |
|---|---|
expression; | Evaluates the expression and discards its value. |
{ ... } | A block. Wherever a single statement is allowed, a block is too. |
if (c) s | Runs s when c is true. |
if (c) s else t | Runs s when c is true, t otherwise. |
for (e in c) s | Runs s once for each element of c, binding it to e. |
for (e : M!Type in c) s | The same, with the iterator typed. |
while (c) s | Runs 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
| Syntax | Meaning |
|---|---|
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.
| Syntax | Returns | Meaning |
|---|---|---|
x.isDefined() | Boolean | False when x is null or the empty string, true otherwise. |
x.isUndefined() | Boolean | The negation of isDefined(). |
x.ifUndefined(v) | Any | x when it is defined, v otherwise. |
x.isTypeOf(T) | Boolean | True when x has exactly type T. |
x.isKindOf(T) | Boolean | True when x has type T or a subtype of it. |
x.instanceOf(T) | Boolean | The same as isKindOf. |
x.hasProperty('name') | Boolean | True when x has a feature with that name. |
x.owningModel() | model | The model that owns x, or null. |
x.nativeType() | class | The underlying implementation class of x. |
x.asString() | String | The printed form of x. |
x.asInteger() | Integer | Parses x as an integer. |
x.asReal(), x.asDouble(), x.asFloat(), x.asLong() | number | Parses x as the named number kind. |
x.asBoolean() | Boolean | Parses x as a boolean. |
x.isInteger() | Boolean | True when x parses as an integer. |
x.isReal() | Boolean | True when x parses as a real. |
x.asDate('dd/MM/yyyy') | Date | Parses x with the given pattern. |
x.asUnicode() | String | Reads x as a hexadecimal code point and returns that character. |
x.format('%.2f') | String | Formats x with the given format string. |
x.print() | Any | Writes x to the output and returns x. |
x.println() | Any | The same, followed by a line break. |
x.print(prefix, suffix) | Any | Writes x framed by the two texts. println takes the same pair. |
x.err(), x.errln() | Any | Writes x to the error stream, which is not shown with the result. |
x.asVar('n') | Any | Binds x to a new variable n and returns x. |
x.asSequence(), x.asSet(), x.asBag(), x.asOrderedSet() | collection | Wraps a single value in a collection of that kind. |
x.size() | Integer | 1 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.
| Syntax | Returns | Meaning |
|---|---|---|
s.length() | Integer | The number of characters. |
s.characterAt(i) | String | The character at index i, as a one-character string. |
s.toCharSequence() | Sequence | The characters, each as a one-character string. |
s.firstToUpperCase(), s.ftuc() | String | The string with its first character upper-cased. |
s.firstToLowerCase(), s.ftlc() | String | The string with its first character lower-cased. |
s.pad(width, ' ', true) | String | Pads s to width with the given text, on the right when the last argument is true and on the left otherwise. |
s.isSubstringOf(t) | Boolean | True when s occurs inside t. |
s.replace(regex, r) | String | Replaces every match of the regular expression with r. |
s.replaceLiteral(l, r) | String | Replaces every occurrence of the literal text l with r. |
s.escapeXml() | String | Escapes the characters that are special in XML text. |
s.toEnum() | enumeration literal | Resolves a label of the form 'M!Status#active' to the literal it names. |
Number operations
| Syntax | Returns | Meaning |
|---|---|---|
n.abs() | number | Absolute value. |
n.floor() | Integer | Largest integer not greater than n. |
n.ceiling() | Integer | Smallest integer not less than n. |
n.round() | Integer | Nearest integer. |
n.min(m), n.max(m) | number | The smaller, the larger of the two. |
n.pow(m) | number | n raised to the power m. |
n.ln(), n.log() | Real | Natural logarithm. |
n.log10() | Real | Logarithm base 10. |
n.factorial() | Integer | The factorial of n. |
i.mod(m) | Integer | The remainder of i divided by m. |
i.to(j) | Sequence | The integers from i to j inclusive, counting down when j is smaller. |
i.iota(j, step) | Sequence | The integers from i to j in increments of step. |
i.toBinary(), i.toHex() | String | The base-2, base-16 spelling of i. |
Date operations
| Syntax | Returns | Meaning |
|---|---|---|
d.getDayOfTheWeek() | Integer | The day of the week of d. |
d.getDifferenceInDays(e) | Integer | The 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.
| Syntax | Returns | Meaning |
|---|---|---|
c.size() | Integer | The number of elements. |
c.isEmpty(), c.notEmpty() | Boolean | Whether the collection has no elements, has some. |
c.first(), c.second(), c.third(), c.fourth(), c.last() | element | The element at that position, or null when the collection is shorter. |
c.first(n) | collection | The first n elements. |
c.at(i) | element | The element at index i, counting from zero. |
c.indexOf(x) | Integer | The index of x, or -1. |
c.random() | element | An arbitrary element. |
c.includes(x), c.excludes(x) | Boolean | Whether x is present, absent. |
c.includesAll(d), c.excludesAll(d) | Boolean | Whether every element of d is present, absent. |
c.count(x) | Integer | How many times x occurs. |
c.including(x), c.excluding(x) | collection | A copy with x added, removed. |
c.includingAll(d), c.excludingAll(d) | collection | A copy with every element of d added, removed. |
c.removeAt(i) | element | Removes the element at index i and returns it. |
c.invert() | collection | A copy in the opposite order. |
c.flatten() | collection | A copy with nested collections spliced in. |
c.clone() | collection | A shallow copy. |
c.createCollection() | collection | A new empty collection of the same kind. |
c.powerset() | Set | Every subset of c. |
c.sum(), c.product() | number | The sum, the product of the elements. |
c.min(), c.max() | number | The smallest, the largest element. |
c.min(d), c.max(d) | number | The same, with d returned for an empty collection. |
c.concat() | String | The printed elements joined end to end. |
c.concat(', ') | String | The same, separated by the given text. |
c.selectByType(T) | collection | The elements whose type is exactly T. |
c.selectByKind(T) | collection | The elements of type T or a subtype. |
c.asSequence(), c.asBag(), c.asSet(), c.asOrderedSet() | collection | A copy as that collection kind. |
c.asConcurrentBag(), c.asConcurrentSet() | collection | A copy safe for concurrent modification. |
c.stream(), c.parallelStream() | Java stream | The 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 |.
| Syntax | Returns | Meaning |
|---|---|---|
c.select(e | p) | collection | The elements for which p holds. |
c.reject(e | p) | collection | The elements for which p does not hold. |
c.selectOne(e | p) | element | One element for which p holds, or null. |
c.selectFirst(e | p) | element | The same. |
c.rejectOne(e | p) | collection | A copy with one matching element removed. |
c.find(e | p) | element | Behaves as selectOne. |
c.findOne(e | p) | element | Behaves as selectOne. |
c.collect(e | x) | collection | The value of x for each element. |
c.exists(e | p) | Boolean | True when p holds for at least one element. |
c.forAll(e | p) | Boolean | True when p holds for every element. |
c.count(e | p) | Integer | How many elements satisfy p. |
c.one(e | p) | Boolean | True when exactly one element satisfies p. |
c.none(e | p) | Boolean | True when no element satisfies p. |
c.nMatch(e | p, n) | Boolean | True when exactly n elements satisfy p. |
c.atLeastNMatch(e | p, n) | Boolean | True when at least n elements satisfy p. |
c.atMostNMatch(e | p, n) | Boolean | True when at most n elements satisfy p. |
c.sortBy(e | x) | Sequence | The elements ordered by the value of x. |
c.closure(e | x) | collection | Every element reachable by applying x over and over. |
c.mapBy(e | k) | Map | The elements grouped into sequences under the key k. |
c.aggregate(e | k, v, init) | Map | Folds 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') | Any | Binds 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
| Syntax | Returns | Meaning |
|---|---|---|
m.get(k), m[k] | value | The value under key k, or null. |
m.getOrDefault(k, d) | value | The value under k, or d. |
m.put(k, v), m[k] = v; | value | Stores v under k. |
m.putIfAbsent(k, v) | value | Stores v only when k has no value. |
m.remove(k) | value | Removes k and returns the value it held. |
m.containsKey(k), m.containsValue(v) | Boolean | Whether the key, the value is present. |
m.keySet() | Set | The keys. |
m.values() | collection | The values. |
m.size(), m.isEmpty() | Integer, Boolean | The number of entries, whether there are none. |
m.clear() | - | Removes every entry. |
m.putAll(n) | - | Copies every entry of n into m. |
Model navigation
| Syntax | Returns | Meaning |
|---|---|---|
x.feature | value | The value of a structural feature. |
x?.feature | value | The same, yielding null when x is null. |
c.feature | Sequence | The feature read off every element of the collection. |
x.eClass() | metaclass | The metaclass of x. Its name gives the type name. |
x.eContainer() | object | The object that contains x, or null for a root. |
x.eContents() | collection | The objects x contains directly. |
x.eCrossReferences() | collection | The objects x refers to without containing them. |
x.eIsSet(f) | Boolean | Whether the feature is set. |
x.id() | String | The identifier of x within the model. |
Type.all | collection | Every instance of Type or a subtype. |
Type.allInstances | collection | The same. |
Type.allOfKind | collection | The same. |
Type.allOfType | collection | Every instance whose type is exactly Type. |
M!Type.all | collection | Any of the above written with the model name, needed when a type name is ambiguous. |
M.allContents() | collection | Every object in the model. |
M.getElementById(id) | object | The object with that identifier, or null. |
M.getElementId(x) | String | The identifier of x. |
M.getTypeNameOf(x) | String | The type name of x. |
M.getFullyQualifiedTypeNameOf(x) | String | The type name of x qualified by its package. |
M.owns(x) | Boolean | Whether x belongs to the model. |
M.hasType('Name') | Boolean | Whether 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
| Syntax | Meaning |
|---|---|
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!Type | Creates 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.
| Syntax | Meaning |
|---|---|
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.