Show in Frame No Frame
Up Previous Next Title Page Index Contents Search

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.

Show in Frame No Frame
Up Previous Next Title Page Index Contents Search