5.2.6 Modularization and recursion
In order to go through all State [Watch] objects by their
Transition relationships, until we reach an object we have already seen, we need
to store the information about the objects we have visited and check each new
object against that list. If the object can be found from the list, we know that
we have completed the full cycle and can stop. So what we need to do
is:
1) | Initialize
a variable to store the list of objects we have
visited. |
2) | Fetch
the first State
[Watch] |
3) | Check
we have not already visited the current State
[Watch] |
4) | Output
the name of the current State
[Watch] |
5) | Add
the current State [Watch] to the
variable |
6) | Navigate
to the next State [Watch] in our
cycle |
7) | Go
back to 3) |
To implement the above in MERL
we need a subgenerator that carries out steps 3–7, calling itself at the
end to implement the recursion. A subgenerator is just a normal generator
definition, executed from within another generator. The subgenerator is executed
in the same context as the parent generator is when the call is made. It thus
has the same element stack as the parent, the same stack of output streams, and
the same global variables (but its own new set of local variables). See the
definition of the
subreport command in
the Section
6.3.12 for more details
and ways of using subgenerators.
To keep track of the visited objects, we need a unique
identifier for each. The name might work in some cases, but more reliable is to
use the unique identifiers produced by the oid command, whose output looks like
3_123 and is guaranteed unique within a
repository. We can simply append the oid of each object to a string variable,
with a space as a separator: ' 3_123 3_129
3_158 '. This lets us see if an object is in the list with a simple
=~ wildcard comparison, e.g.
'* 3_158 *' matches but a new
'* 3_222 *' does not. Having the spaces
saves us from mistakenly matching
'*3_222*' with
'3_2221'.
The subgenerator for this case can thus be:
01 _handleStateAndRecurse()
02 if not $visited =~ oid%wildsp then
03 if $visited <> ' ' then
04 ', '
05 endif
06 id
07 variable 'visited' append oid ' ' close
08 do ~From~To.()
09 { _handleStateAndRecurse() }
10 endif
The if
clause around the whole generator in line 2 checks that the current State
[Watch] object has not already been visited (note the use of the
%wildsp translator, which adds the
spaces and wildcard asterisks around the oid). If we are good to go, lines 3
– 6 will output the name of the current element. As we want the names to
appear as a comma-separated list and without any additional commas at the end of
the list, we use a little trick in lines 3 – 5: we check if we have
already visited any objects and if we have, we will output
', ' before the actual name. This means
that all but the first name in the list will be preceded by a comma and a space.
After outputting the name, line 7 appends the current object’s unique
identifier and a space to the list of visited objects, and lines 8 – 9
take care of the next iteration by calling this same subgenerator
recursively.
| As
the _handleStateAndRecurse subgenerator operates in the context of a
WatchApplication graph type, we want to define it for that graph type, not for
the WatchFamily graph type that the SpecSheet generator was defined for. You can
open another Generator Editor, or change the Graph type of the current Editor
with Generator | Change Graph Type.... This kind of division of responsibility
to different types is typical for object-oriented programming and the
object-oriented principles also work in cases where a generator is applicable to
more than one graph type: there is no need to duplicate it to all graph types as
it can be defined in a common supertype, or in the supertype of all graph types,
Graph itself, which makes it available for all of
them. |
With all the aforementioned
changes, our SpecSheet generator will now look like this:
01 SpecSheet()
02 _translators()
03 foreach >Watch; orderby y num
04 { filename
05 subreport '_default directory' run
06 id%file '.txt'
07 write
08 'Watch: ' id newline
09 do .Display
10 { ' Number of time units shown: '
11 @count = '0'
12 do :UnitZones { @count++%null }
13 @count newline
14 ' Buttons: '
15 dowhile :Buttons { id ', ' }
16 newline
17 }
18 do .LogicalWatch
19 { ' Apps: '
20 do decompositions
21 { foreach .Start [Watch]
22 { $visited = ' '
23 do ~From~To.()
24 { _handleStateAndRecurse() }
25 }
26 }
27 newline
28 }
29 newline
30 close
31 }
The use of the recursive subgenerator requires one
addition to our SpecSheet() MERL code. In line 22 we initialize the
$visited variable. It is a global
variable that stores the list of visited objects and is accessed and updated in
the _handleStateAndRecurse subgenerator. After these changes, the generator
output in Ace.txt will now look like
this:
Ace
Number of time units shown: 3
Buttons: Mode, Set, Up, Down
Apps: Time, AlarmClock, Stopwatch, Timer, WorldTime
So
far our target output has been plain text without any real layout. In the next
chapter we will discuss how to format the output for a specific
purpose.