Template operations provide a way to re-use small fragments of
EGL code. This article shows how to write
EGL template operations and discusses when you might want to use them.
Suppose we are writing a code generator for plain-old Java objects, and we have the following EGL code (which assumes the presence of a class object):
class [%=class.name%] {
[% for (feature in class.features) { %]
/**
* Gets the value of [%=feature.firstToLowerCase()%]
*/
public [%=feature.type%] get[%=feature%]() {
return [%=feature.firstToLowerCase()%];
}
/**
* Sets the value of [%=feature.firstToLowerCase()%]
*/
public void set[%=feature%]([%=feature.type%] [%=feature.firstToLowerCase()%]) {
this.[%=feature.firstToLowerCase()%] = [%=feature.firstToLowerCase()%];
}
[% } %]
}
While the above code will work, it has a couple of drawbacks. Firstly, the code to generate getters and setters cannot be re-used in other templates. Secondly, the template is arguably hard to read - the purpose of the loop's body is not immediately clear.
Using EGL template operations, the above code becomes:
class [%=class.name%] {
[% for (feature in class.features) { %]
[%=feature.getter()%]
[%=feature.setter()%]
[% } %]
}
[%
@template
operation Feature getter() { %]
/**
* Gets the value of [%=self.firstToLowerCase()%]
*/
public [%=self.type%] get[%=self%]() {
return [%=self.firstToLowerCase()%];
}
[% } %]
@template
operation Feature setter() { %]
/**
* Sets the value of [%=self.firstToLowerCase()%]
*/
public void set[%=self%]([%=self.type%] [%=self.firstToLowerCase()%]) {
this.[%=self.firstToLowerCase()%] = [%=self.firstToLowerCase()%];
}
[% }
%]
Notice that, in the body of the loop, we call the template operations,
getter and
setter, to generate the getter and setter methods for each feature. This makes the loop arguably easier to read, and the
getter and
setter operations can be re-used in other templates.
Template operations are annotated with
@template and can mix dynamic and static sections, just like the main part of an EGL template. Operations are defined on metamodel types (Feature in the code above), and may be called on any model element that instantiates that type. In the body of an operation, the keyword
self is used to refer to the model element on which the operation has been called.
Common issues
Issue: my template operation produces no output.
Resolution: ensure that the call to the template operation is placed in a dynamic output section (e.g.
[%=thing.op()%]) rather than in a plain dynamic section (e.g.
[% thing.op(); %]). Template operations return a value, which must then be emitted to the main template using a dynamic output section.
Thanks to Mark Tippetts for
reporting this issue via the Epsilon forum.