3.3 The Code Generator
The basic idea of the code generator within a DSM environment
is simple: it crawls through the models, extracts information from them and
translates it as the code for the target platform. With the models capturing all
static and dynamic logical aspects and a framework providing the required
low-level support, the code generator can produce completely functional and
executable output code.
The MetaEdit+ generator used to produce the code also
provides a flexible tool to automate integration of the DSM environment with
other tools. An example of this kind of integration within the watch example is
the auto-build mechanism that enables us to automatically compile the generated
code and execute it on a testing environment. This automation results in major
savings of both the effort and time during the testing.
In the watch example, the auto-build proceeds with the
following steps:
| 1) |
The scripts for compiling and executing the code are generated. As the
mechanism of automated compilation and execution varies between the platforms,
the number and content of generated script files depend on the target
platform. |
| 2) |
The code for framework components is generated. All framework code has
been included into the watch models and generated from there as needed. This way
we ensure that all required components are available at the time of compilation
and have the control over the inclusion of platform specific
components. |
| 3) |
The code for logical watches and watch applications is generated. The
state machines are implemented by creating a data structure that defines each
state transition and display function. For each action the code generator
creates a set of commands that are executed when the action is
invoked. |
| 4) |
The generated code is compiled and executed as a test environment for the
target platform. Basically this step requires only the execution of the scripts
created during the first step. |
How then has
the code generator been implemented? As we have seen before, the code generators
in MetaEdit+ are defined with a dedicated MERL generator definition language.
Each generator is associated with a certain graph type, and thus can operate on
models made according to that specific graph type. These generators can be
arranged in a hierarchical fashion with generators calling sub-generators. The
top level of the code generator architecture of the watch example (i.e. the
generators associated with the WatchFamily graph type) is presented in
Figure 3-3 (a ‘*’ in the name of
a generator denotes an individual version for each target platform).

Figure 3-3. The watch code generator architecture, part 1
On
the top, there is a master generator called ‘Autobuild’. The role of
‘Autobuild’ is similar to those of the main programs in most
programming languages: it initiates the whole generation process but basically
does not contain anything else but the calls to the sub-generators on the lower
level. The sub-generators on the next level relate closely to those steps of the
auto-build process presented earlier in this chapter. As
‘_JavaComponents’ only outputs the pre-defined Java code for the
framework components and ‘_compile and execute *’ only executes
scripts produced during the earlier steps of the generation process, we can
concentrate more on the more complex sub-generator, ‘_create make for
*’ and ‘_Models’.
The basic task of the ‘_create make for *’
sub-generators is to create the executable scripts that will take care of the
compilation and execution of the generated code. As this procedure varies
between platforms, there is an individual version of this sub-generator for each
supported target platform. If there are any specific platform-related generation
needs like HTML pages for the browser-based test environment in
Figure 3-3, they can be integrated with the
‘_create make for *’ sub-generator.
The responsibility of the ‘_Models’ and
‘_Model’ sub-generators is to control the generation of code for the
watch models, logical watches and watch applications. For each watch model,
three pieces of code are generated: an applet as the physical implementation of
the user interface, a display definition about the specific user interface
components of that watch and the definition of the logical watch.
An example of the code generated for an applet (produced
by the ‘_Applet’ sub-generator) is shown in
Listing 1. The generated code defines the
applet as an extension of the AbstractWatchApplet and adds the initialization
for the new class.
public class Venturer extends
AbstractWatchApplet
{
public Venturer()
{
master=new Master();
master.init(this, new DisplayX334(),
new TASTW(master));
}
}
Listing 1. Generated code for an applet
The display
definition can be generated in the same vein by the sub-generator
‘_Display’, as shown in
Listing
2. Again, a new concrete display class is inherited from the AbstractDisplay
and the required user interface components are defined within the class
constructor method.
public class DisplayX334 extends
AbstractDisplay
{
public DisplayX334()
{
icons.addElement(new Icon("alarm"));
icons.addElement(new Icon("stopwatch"));
icons.addElement(new Icon("timer"));
times.addElement(new Zone("Zone1"));
times.addElement(new Zone("Zone2"));
times.addElement(new Zone("Zone3"));
buttons.addElement("Mode");
buttons.addElement("Set");
buttons.addElement("Up");
buttons.addElement("Down");
}
}
Listing 2. Generated code for a display
However, to
understand how the code for a logical watch and a watch application is
generated, we need to explore the code generator architecture further. The lower
level of the architecture (i.e. the sub-generators associated with the
WatchApplication graph type) is presented in
Figure 3-4.

Figure 3-4. The watch code generator architecture, part 2
The
sub-generators ‘_JavaFile’ (which is the same as in
Figure 3-3) and ‘_Java’ take
care of the most critical part of the generation process: the generation of the
state machine implementations. To support the possibility to invoke a state
machine from within another state machine in hierarchical fashion, a recursive
structure was implemented in the ‘_JavaFile’ sub-generator. During
the generation, when a reference to a lower-level state machine is encountered,
the ‘_JavaFile’ sub-generator will dive to that level and call
itself from there.
The task of the ‘_Java’ sub-generator is to
generate the final Java implementation for the logical watch and watch
application state machines. An example of such code can be found in
Listing 3, which shows the implementation of
the Stopwatch application.
1 public class Stopwatch extends AbstractWatchApplication
2 {
3 static final int a22_3324 = +1; //+1+1+1+1
4 static final int a22_3621 = +1+1; //+1+1+1
5 static final int a22_4857 = +1+1+1; //+1+1
6 static final int d22_4302 = +1+1+1+1; //+1
7 static final int d22_5403 = +1+1+1+1+1; //
8
9 public METime startTime = new METime();
10 public METime stopTime = new METime();
11
12 public METime getstartTime()
13 {
14 return startTime;
15 }
16
17 public void setstartTime(METime t1)
18 {
19 startTime = t1;
20 }
21
22 public METime getstopTime()
23 {
24 return stopTime;
25 }
26
27 public void setstopTime(METime t1)
28 {
29 stopTime = t1;
30 }
31
32 public Stopwatch(Master master)
33 {
34 super(master, "22_1039");
35 addStateOop("Start [Watch]", "22_4743");
36 addStateOop("Running", "22_2650");
37 addStateOop("Stopped", "22_5338");
38 addStateOop("Stop [Watch]", "22_4800");
39
40 addTransition ("Stopped", "Down", a22_3324, "Stopped");
41 addTransition ("Running", "Up", a22_4857, "Stopped");
42 addTransition ("Stopped", "Up", a22_3621, "Running");
43 addTransition ("Stopped", "Mode", 0, "Stop [Watch]");
44 addTransition ("Running", "Mode", 0, "Stop [Watch]");
45 addTransition ("Start [Watch]", "", 0, "Stopped");
46
47 addStateDisplay("Running", -1, METime.SECOND, d22_5403);
48 addStateDisplay("Stopped", -1, METime.SECOND, d22_4302);
49 }
50
51 public Object perform(int methodId)
52 {
53 switch (methodId)
54 {
55 case a22_3324:
56 setstopTime(getstartTime().meMinus(getstartTime()));
57 return null;
58 case a22_3621:
59 setstartTime(getsysTime().meMinus(getstopTime()));
60 iconOn("stopwatch");
61 return null;
62 case a22_4857:
63 setstopTime(getsysTime().meMinus(getstartTime()));
64 iconOff("stopwatch");
65 return null;
66 case d22_4302:
67 return getstopTime();
68 case d22_5403:
69 return getsysTime().meMinus(getstartTime());
70 }
71 return null;
72 }
73 }
Listing 3. The generated code for the Stopwatch application
For
a thorough understanding of the ‘_Java’ sub-generator output, let us
study the generated code line by line. As previously, a new concrete watch
application class is derived from the AbstractWatchApplication class (line 1).
From here on, the responsibility for the generated code is distributed among the
‘_Variables’, ‘_StateData’,
‘_TransitionData’, ‘_StateDisplayData’,
‘_Actions’ and ‘_DisplayFns’ sub-generators.
The ‘_Variables’ and ‘_getSet’
sub-generators are responsible for declaring the identifiers for actions and
display functions to be used later within the switch-case structure (lines
3–7). They also define the variables used (lines 9–10) and the
implementations of their accessing methods (lines 12–30). A short return
back to the ‘_Java’ sub-generator produces the lines 32–34,
followed by the state (lines 35–38) and state transition definitions
(lines 40–45) generated by the ‘_StateData’ and
‘_TransitionData’ sub-generators. The
‘_StateDisplayData’ and ‘_StateDisplayDataContent’
sub-generators then provide the display function definitions (lines 47–48)
while the basic method definition and opening of the switch statement in the
lines 51–54 again come from the ‘_Java’
sub-generator.
The generation of the code for the actions triggered
during the state transitions (lines 55–65) is a good example of how to
creatively integrate the code generator and the modeling language. On the
modeling language level, each action is modeled with a relationship type of its
own. When the code for them is generated, the ‘_Actions’
sub-generator first provides the master structure for each action definition and
then executes the sub-generator bearing the same name as the current action
relationship (either ‘_Icon’, ‘_Roll’,
‘_Alarm’ or ‘_Set’). This implementation not only
reduces the generator complexity but also provides a flexible way to extend the
watch modeling language later if new kinds of actions are needed.
Finally, the ‘_DisplayFns’ and
‘_calcValue’ sub-generators produce the calculations required by the
display functions (lines 66–69). The ‘_calcValue’ –
which is also used by the ‘_Alarm’ and ‘_Set’
sub-generators – provides the basic template for all arithmetic operations
within the code generator.
The generation of a logical watch proceeds in the same
way. As there are typically no actions related to the state transitions within a
logical watch, they are left out of the generated code as well. Moreover, in
order to support the references to the lower-level state machines (i.e. to the
watch applications the logical watch is composed of), the definitions of these
decomposition structures must be generated. This is taken care of by the
‘_Decompositions’ sub-generator.
As we can see, there is no magic within the code
generator, just a carefully designed modularization of the solution and
integration with the modeling language and domain framework. In general, the
rule is to try to keep generation as simple as possible: if anything appears
difficult, consider raising it up into the modeling language, or pushing it down
into the domain framework code.
Having covered the modeling language and the code
generator, we will next discuss issues related to the domain framework
code.