5.3.8 Hints and tips
In most cases writing a generator definition is a reasonably
straightforward task, but it is still sometimes good to be aware of certain
existing solutions or techniques for advanced use of MERL. In this section we
give some hints and tips related to MERL.
Context
While writing generator definitions, it is important to keep
in mind the context you are operating on. For example, when diving through an
explosion link, the context changes from the original graph to that of the
explosion graph, which may be of a different graph type. This means that the
parts of the generator definition that handle the explosion graph must be
written according to that graph’s metamodel. Similarly, when accessing
properties, you may encounter a situation where a non-property is used as a
property, and therefore you have to be aware of what kind of information you can
retrieve from it.
Loop-backs
Navigating via a relationship while navigating through
bindings may sometimes produce unexpected results. Consider the following
expression when navigating from an object:
do >Transition.()
What we probably wanted to do
with this was to access all objects that the current object is connected to via
a ‘Transition’ relationship. However, navigating to the relationship
leaves us ‘in’ that relationship, and when we try to navigate from
there to objects, we can get to not only the object we wanted, but also back to
the object we started from.
The simplest solution is to specify the role types,
effectively making the relationship directed:
do ~From>Transition~To.()
If those role types are
not used with other relationship types, it is also be possible to omit the
relationship entirely, and navigate from the From role to all To roles contained
in the same binding:
do ~From~To.()
Even if the role types at either end
of this binding are of the same type, this is still possible. Navigating in one
step from a role to roles, there is no way to get from the role back to itself.
For instance, if there is an Association relationship with just Involved
roles:
do ~Involved~Involved.()
If there is a need to check
the type of the relationship in this kind of construction, remember that we
cannot insert >Association between the roles. That would bring us back to the
same problem we started with, i.e. from the Association we could get back to the
same Involved role we came from. Instead, we can check the relationship type
with an if statement:
do ~Involved~Involved
{ if >Association then
do .()
{ ... }
endif
}
Subgenerators and modularization
Subgenerators provide a useful mechanism for
modularization
of generator definitions. However,
organizing and calling subgenerators can get fairly complex without a good
generator hierarchy. A good technique to support generator hierarchies is to use
the name of a type as the name for the generator definition related to that
type. This enables you to call subgenerators using the
type keyword to define the name of the
executed subgenerator:
subreport type run
The obvious benefit of this
approach is that it saves the metamodeler from writing multiple
if clauses when deciding which
subgenerator to run when there are several available. This solution is also
extendable: if a new type becomes available as one choice among others, it can
be handled just by adding a generator of the same name for it.
For practical examples of using subgenerators, please see
the code generator for the Watch example. In general, it is good to use a prefix
before the type name, e.g. to distinguish between different programming
languages. This also allows the Generator Editor Hierarchy view to tell that the
subreport call is to one of a limited number of generators, rather than
apparently to any generator.
subreport '_Java_' type run
Checking instance uniqueness
To check that a given object type only has one instance per
graph, you would normally use an Occurrence Constraint in the graph type. You
could also use the following generator definition:
Report 'Check XYZ instances'
subreport '_translators' run
$occurrences='0'
foreach .XYZ { $occurrences++%null }
if $occurrences > '1' num then
'Object type XYZ found '
$occurrences
' times in this graph:'
foreach .XYZ
{ id newline
}
endif
endreportWhen the generator is run on a graph containing
more than one occurrence of XYZ, an output window will report the number and
names of XYZ objects. Each object name can be double-clicked to take the user to
the object. If no duplicates are found, no output window is opened.
Autobuild and external processing
If you need to perform actions after generation, you can use
the generator language to export the data you need to a file, and then have it
call another program to operate on that data. For instance, the Autobuild
generator in the Watch example generates the code and then calls a compiler, and
the Export to Word generator starts Word and calls a macro in the Word template
supplied.
Parameterizing generators
If you have state that should persist between generator runs,
e.g. to parameterize generators, a good solution is to have that information as
properties in the model, e.g. of the top level graph from which the generator is
run. To separate these parameters from the top level graph, you can store them
in a separate graph type which just holds the parameters and a link to the top
level graph.
If the parameters are temporary, you could use
prompt...ask. If the parameters are per
user, and should apply to all generation for that user, you could use text files
in the working directory, read with
filename...read and assigned to
variables as necessary (a regex translator could easily separate the left and
right parts of a line in .ini file
format).