5.1.1 Creating a simple generator
Now it is time to write our first generator. Assuming that we
are creating a new generator for the WatchApplication Graph type in our demo
repository, we can open the editor by opening the ‘Digital Watch’
project, selecting ‘Simple’ (or any other Graph of type
WatchApplication) in the
Graphs list and choosing
Edit Generators
from the popup menu. This will open the Generator Editor as shown below in
Figure 5–1:
Figure 5–1. Generator Editor.
(If the contents of the
top-left list look significantly different, perhaps you opened it on a different
Graph type. You can change Graph types with Generator | Change Graph
Type... or the corresponding toolbar button.)
To create a new empty generator definition, press the
New button on the toolbar (or
Ctrl+N or
Generator | New...
from the menu bar), and enter Connections() as a name for the generator when
prompted. You will now see the new generator ready for editing at the bottom of
the Generator Editor window (as in
Figure
5–2).
Figure 5–2. Generator Editor with new generator definition.
Let
us now enter the following MERL code into the editing area (without the line
numbers, they are here just to provide reference points) and press Save
in the toolbar (or Ctrl+S or Generator | Save from the menu
bar):
01 Connections()
02 foreach .State [Watch]
03 { 'State : '
04 :State name;
05 newline
06 'Connects to: '
07 newline
08 do ~From>()~To.State [Watch]
09 { ' '
10 :State name;
11 newline
12 }
13 newline
14 }
This short piece of code illustrates the basic
features of a generator definition. The name of the generator is given in the
header, either just as the name followed by
(), as above, or with the older
Report 'name' ... EndReport structure
around the generator code (for more information, see Section
6.2.1). Since we edit one generator at a
time, rather than having many in the same text, by convention we do not start a
new level of indentation after the header.
Generators operating on a graph, like our example here,
can access the graph’s member elements like objects, relationships, roles
and ports with a top-level foreach
loop. In the above example (lines 2 – 14), we loop through the instances
of the ‘State [Watch]’ object type and retrieve specific information
from them to output. For each state, we want to output its name, followed by a
list of the states it is connected to. After the
foreach command you will notice the use
of the ‘.’ prefix character
before the name ‘State [Watch]’. The prefix characters
‘.>~#:’ make the
distinction between Objects, Relationships, Roles, Ports and Properties. For
example, in line 2 the ‘.’
preceding the type name tells the generator to fetch objects matching that type
name.
In line 3 we enter the loop: lines 3 – 14 will be
executed once for each ‘State [Watch]’ in the graph. Outside the
loop we can say that we are ‘in’ the graph; within the loop we can
say that we are ‘in’ a ‘State [Watch]’ object.
In lines 3 – 5 we first output the string
‘State : ’, then the value of the ‘State name’ property
of the current ‘State [Watch]’ object and then complete the output
with a line break. Again, please note the ‘:’ prefix before
‘State name’ that denotes that a property will follow. Lines 6
– 7 simply output the string ‘Connects to: ’ followed by a
line break.
As you can see, none of these output commands need to
specify
where the output is going: it is going to the current output
stream which, by default, just goes to a window, whose result will be shown to
the user at the end of the generation. It is also possible to direct the output
elsewhere, e.g. to a file or to build up a variable. This will be discussed
further in the Primer in Section
5.2.4, and the relevant commands are
documented in Section
6.4.1 and
6.5.2.
Lines 8 – 12 contain an important example of a
fundamental feature of MERL: navigation through bindings. In this case, we want
to find out all other ‘State [Watch]’ objects the current object is
connected to, so we use the do command
to loop through all of them. After the do command you will see the following
token:
~From>()~To.State [Watch]
Though this may look a
bit cryptic at first glance, it just says to find all ‘From’ roles
for the current object first, then follow them through the relationship in that
binding to the respective ‘To’ roles and thence to the ‘State
[Watch]’ objects connected to these roles. The ‘~’ prefixes
denote the roles and the ‘>’ prefix refers to the relationship.
The only extra trick here is that – unlike previously where we used exact
type names to access design elements – we now use the wildcard
() token after ‘>’ to
access any kind of relationship that is bound to the ‘From’ roles.
Within the body of this inner loop we thus iterate over
all State objects that are reachable in one From-To ‘hop’ from the
outer loop’s State. The content of the inner loop is pretty clear: output
some spaces, then the name of the accessed object and a line break. Note how the
context has changed within the loop: the current element is now the target State
object, whereas in the outer loop it is the source State object. The output of
:State Name; in line 10 is thus
different from the output of :State
Name; in line 4.
After the inner
do loop the generator definition is
reasonably trivial, just outputting a line break before the main loop is run
again for the next State. Note that we are now back in the outer loop, so
:State name; here would again refer to
the same element as in line 4.
Before we proceed with the execution of our example
generator, it is worth noting a few things about the MERL keywords and generator
names. As a general rule, generator keywords should always be preceded by white
space (space, tab, or carriage return), and for backwards compatibility they can
optionally be followed by a semicolon,
‘;’. Type names that
contain spaces, or that are followed on the same line by another command, should
be followed by a semicolon. This is not needed in the chaining of type names,
where the next type prefix
‘.>~#:’ is sufficient to
terminate the previous type name, e.g.
‘.State
[Watch]~To’.
Generators whose names
begin with
'_' are hidden from the list shown to the user when choosing
Generate...
elsewhere than the Generator Editor (e.g. from a Diagram Editor). Names like
this should thus be used for generators that are intended only for use as
subgenerators, e.g. if they assume that the generation output is already going
to a file, or that something other than a graph is already on the generator
stack. If necessary, a user can still see and even run these generators
elsewhere by holding down Shift while choosing
Generate....