5.3.1 Getting started with MERL
MERL is a scripting language with commands for navigating
through design models, extracting information from design elements and
outputting it to a window or file. There are also a number of additional utility
commands that enable for example various user interventions or execution of
external programs. These commands are explained in detail in the following
sections, while Section
5.3.9 provides
a quick reference to them.
Before learning about the individual commands, let us
concentrate first on the basics of MERL. Consider the following example
generator definition for the Watch example:
01 Report 'Test'
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 }
15 endreport This example illustrates the basic
features of a generator definition. Generator definitions accessed from the
Generator Browser always carry the header token
Report and the name of the generator
definition (line 1). Similarly, the generator definition is closed by the
endreport token (line 15). These header
and end tokens, however, are not present in those short generator definitions
used in the Symbol Editor or for identifier generators in the Object Tool etc.:
since these are not run manually, they do not need a name.
All generators operating on the graph level, like our
example here, must access their member elements like objects, relationships,
roles and ports within 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 be
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 expect an object as the next retrievable element.
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 specify
where the output is going: all output is simply appended to the current
output stream. By default, the stream 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
output to files (see Section
5.3.5) or
to named internal streams, which can be used as variables (Section
5.3.6). Temporary streams are also used
by some commands to form their arguments, e.g. to build up the name of a file to
print a bitmap of the current graph to, from the name of the graph plus an
extension:
filename :Application name; '.png' print
Lines 8
– 12 contain an important example of a very basic 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 the 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 special() 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. As we descend through loops, we thus build up a
stack: each element on the stack corresponds to the current element in each loop
level. Most often, the information you want to output will be from the current
loop level. If you want information from another model element, it will often be
available in an outer loop level. For instance, if we wanted to output each
source and target state as a pair on a line, we could remove all output from the
outer loop, and have a line in the inner loop that accessed the element from the
outer loop with the ;1
suffix:
:State name;1; ' -> ' :State name;
Using
information like this from outer loops often replaces what would be done with
variables in a generic programming language. This syntax is shorter, removes the
need to define variable names, remember them, and make sure their values are not
overwritten before they are used. For the relatively rare cases where variables
are still needed, MERL offers powerful associative variables along with a simple
shortcut syntax for common cases: see Section
5.3.6.
After the inner
do loop the generator definition is
reasonably trivial, just setting a line break before the main loop is iterated
again. Note that we are not back in the outer loop, so
:State name; here would refer again to
the same element as in line 4.
As a general rule, generator keywords should always be
preceded by white space (space, tab, or carriage return) and they can be
optionally followed by a semicolon, ‘;’. Type names that contain
spaces 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’.