5.2.5 Navigation revisited
In the initial version of our example generator we used
foreach and
do loops, without any filtering or
explicit ordering, to go through the elements on the Graph level, to access
property values and subgraph links, and to navigate along roles and
relationships. While this approach seems to work fairly well in our limited
example, it is not perfect by any means. For example, the outermost
foreach loop handles the WatchModel
relationships in the default alphabetical order, which may not be the order we
want them to appear in our spec sheet. If we want them to appear in the same
order as they appear vertically in our diagram, we can augment the
foreach command with an
orderby clause:
foreach >Watch; orderby y num
The above can be
read simply as ‘go through all the Watch relationships in ascending order
according to their y coordinates, sorted as numbers’. The
num is important: otherwise they will
be sorted as strings, which places ‘100’ before ‘99’.
Note also the semicolon to show MERL where the relationship type name finishes;
otherwise the rest of the line would be considered part of the type name. (A
line break or open brace ‘{‘ would also suffice to end the type
name, since ‘{‘ cannot be in a literal type name in MERL unless
escaped as ‘\{’.)
Another problem arises when we are outputting the button
names. We used a blank character to separate the names, but what would happen if
we were using the more usual comma character as a separator? The MERL code would
look like this:
do :Buttons { id ', ' }
The example output would now
be:
Buttons: Mode, Set, Up, Down,
This isn’t
ideal, as we don’t want an extra comma after the list of the names. The
simple solution is to replace do with
dowhile: when iterating over the last
element of the loop, dowhile skips all
simple string commands at the end of the loop:
dowhile :Buttons { id ', ' }
The output will now
look like this:
Buttons: Mode, Set, Up, Down
There is also another
ordering problem looming when we fetch the names for applications within the
LogicalWatch’s subgraph. The loop command we are using for this retrieves
the State [Watch] objects in the default alphabetical order. Adding an
orderby clause doesn’t help here,
as the order we want isn’t easily expressible as a sortable value for each
element. The best order is probably that of the Transition relationships, which
define the cycle of applications within the LogicalWatch. The cycle starts from
the start state and flows from each State [Watch] object to the next through the
directional Transition relationships. This requires somewhat more complex
navigation in the subgraph. As there is only one Start in each WatchApplication
graph (as defined by an occurrence constraint), we can use a simple
foreach command to get to the starting
point for our navigation. The MERL code for this and the consequent navigation
to the first State [Watch] object looks like this:
foreach .Start [Watch]
{ do ~From~To.() { /* some code here */ } }
We find the
Start, then navigate along the From role and To role into the object at the
other end. (Here we use the wildcard
.(), meaning any object; we could also
explicitly write .State [Watch].) The
navigation from one State [Watch] to the next is similar:
do ~From~To.()
There is now, however, a catch: as
there is no explicit Stop state in our graph and the number of State [Watch]
objects differs between different graphs, how do we know when to stop looping?
The solution is to keep track of the objects as we pass through them, and stop
when we encounter an object we have already visited. The next section discusses
how this can be done with recursion and
subgenerators.