Up Previous Next Title Page Contents

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, load it into the IDE and execute it. 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 and project files 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 on 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.

For a better understanding of the generator architecture and how the actual code is generated, let us walk through the code generation of WatchApplication state machines. We will use Java as an example language this time, but everything explained here applies equally well to C# – except for the syntactical differences between Java and C#, of course.

The generator hierarchy for the Java of a watch’s behavior is shown in Figure 3-3. This generator modularization remains the same for both Java and C#, and there are even a few subgenerators that are employed by both of them (like ‘_TransitionData’ or ‘_Decompositions’). Where a language-specific subgenerator is needed, the C# version is named with a prefix of ‘_Cs’: ‘_Cs_variables’, ‘_Cs_Actions’, etc. Java generators mostly have no prefix, but a few begin with ‘_Java’.

Figure 3-3. The watch code generator architecture, part 1

In order to see which part of the generator is responsible for what, let us explore the Java code in Listing 1 (generated by running the ‘Java state machine’ generator for ‘Stopwatch’) line by line:
01 package com.metacase.watch.generated;
02
03 import com.metacase.watch.framework.*;
04
05 public class Stopwatch extends AbstractWatchApplication {
06   static final int a22_3324 = 1;
07   static final int a22_3621 = 2;
08   static final int a22_4857 = 3;
09   static final int d22_4302 = 4;
10   static final int d22_5403 = 5;
11
12   public METime startTime = new METime();
13   public METime stopTime = new METime();
14
15   public METime getstartTime() {
16     return startTime;
17   }
18   public void setstartTime(METime t1) {
19     startTime = t1;
20   }
21   public METime getstopTime() {
22     return stopTime;
23   }
24   public void setstopTime(METime t1) {
25     stopTime = t1;
26   }
27
28   public Stopwatch(Master master) {
29     super(master, "22_1039");
30     addStateOop("Start [Watch]", "22_4743");
31     addStateOop("Running", "22_2650");
32     addStateOop("Stopped", "22_5338");
33     addStateOop("Stop [Watch]", "22_4800");
34
35     addTransition ("Stopped", "Down", a22_3324, "Stopped");
36     addTransition ("Running", "Up", a22_4857, "Stopped");
37     addTransition ("Stopped", "Up", a22_3621, "Running");
38     addTransition ("Stopped", "Mode", 0, "Stop [Watch]");
39     addTransition ("Running", "Mode", 0, "Stop [Watch]");
40     addTransition ("Start [Watch]", "", 0, "Stopped");
41
42     addStateDisplay("Running", -1, METime.SECOND, d22_5403);
43     addStateDisplay("Stopped", -1, METime.SECOND, d22_4302);
44   };
45
46   public Object perform(int methodId)
47   {
48     switch (methodId) {
49       case a22_3324:
50         setstopTime(getstartTime().meMinus(getstartTime()));
51         return null;
52       case a22_3621:
53         setstartTime(getsysTime().meMinus(getstopTime()));
54         iconOn("stopwatch");
55         return null;
56       case a22_4857:
57         setstopTime(getsysTime().meMinus(getstartTime()));
58         iconOff("stopwatch");
59         return null;
60       case d22_4302:
61         return getstopTime();
62       case d22_5403:
63         return getsysTime().meMinus(getstartTime());
64     }
65     return null;
66   }
67 }

Listing 1. The generated code for the Stopwatch application

In the beginning, the top-level ‘_Java’ generator takes care of the usual headers and imports (lines 1-3), and derives a new concrete watch application class from the AbstractWatchApplication class (line 5). From here on, the responsibility for the generated code is distributed among the ‘_Variables’, ‘_StateData’, ‘_TransitionData’, ‘_Decompositions’, ‘_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 6–10). They also define the variables used (lines 12–13) and the implementations of their accessing methods (lines 15–26). A short return back to the ‘_Java’ sub-generator produces the lines 28-29, followed by the state (lines 30–33) and state transition definitions (lines 35–40) generated by the ‘_StateData’ and ‘_TransitionData’ sub-generators. The ‘_StateDisplayData’ and ‘_StateDisplayDataContent’ sub-generators then provide the display function definitions (lines 42–43) while the basic method definition and opening of the switch statement in the slines 44–48 again come from the ‘_Java’ sub-generator.

The generation of the code for the actions triggered during the state transitions (lines 49–59) 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 60–63). 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 final lines 64-67 are again provided by the ‘_Action’ subgenerator.

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. To enable the invocation of these lower-level state machines 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. ‘_JavaFile’ will also take care of outputting each state machine implementation into a file of its own.

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.

Although they are the core of the generator, the subgenerators responsible for the generation of the WatchApplication state machine implementation are not enough on their own. A complete and executable application still requires a good chunk of additional code like UI definitions and make or project files depending on the target platform and language. Hence we have established another hierarchy of generators (shown in Figure 3-4) on top of the WatchApplication generation to provide these additional bits and pieces (again, C# versions of subgenerators add the ‘_Cs’ prefix or replace _’Java’ with it in their names).

Figure 3-4. The watch code generator architecture, part 2

The starting point for the generation is the generator called ‘Autobuild’. The role of ‘Autobuild’ is similar to that of the ‘Build’ or ‘Run’ button in an IDE: it initiates the whole generation process, compiles the generated code, and runs the resulting application. These tasks are actually handled in sub-generators, with Autobuild itself containing little else other than the calls to the sub-generators. The sub-generators on the next level relate closely to those steps of the auto-build process presented earlier in this chapter. The generators at this level are as follows:
‘_Watch_translators’ sets the translation shortcuts used in subgenerators.
‘__Paths’ defines the default locations for various add-ons like Java Development Kit etc.
‘_read INI file’ reads and parses the plug-in .ini file
‘_prebuild for *’ generators carry out required initializations for various generation targets
‘_Language Framework’ and subsequent generator ‘_Framework Components’ output the pre-defined code for the components of the selected framework
‘_compile and execute *’ generators only execute scripts produced during the earlier steps of the generation process
‘_create make for *’ generators create the scripts for compiling and executing the generated code
‘_Models’ is the starting point for the generation of the actual watch applications
While most of these are reasonably simple and straightforward, the last two subgenerators, ‘_create make for *’ and ‘_Models’, need to be explored more thoroughly. 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, they can be integrated with the ‘_create make for *’ sub-generator. More information about C# and Java specific versions of ‘_create make for *’ can be found in Sections 4.1Code generator for C#’ and 5.1Code generator for Java’.

It is the responsibility of the ‘_Models’ and the subsequent ‘_Java_WatchModel’ subgenerators to handle the generation of the various Watch models. The ‘_Java_Display’ subgenerator produces the code for the basic UI elements like buttons, icons and timezone slots, whereas the call to the ‘_JavaFile’ subgenerator will round things up and bring us to the generation of the state machine implementations for individual WatchApplications, which we have seen already. An example of the code generated for the X334 display definition is shown in Listing 2.
01 public class DisplayX334 extends AbstractDisplay
02 {
03   public DisplayX334()
04   {
05     icons.addElement(new Icon("alarm"));
06     icons.addElement(new Icon("stopwatch"));
07     icons.addElement(new Icon("timer"));
08
09     times.addElement(new Zone("Zone1"));
10     times.addElement(new Zone("Zone2"));
11     times.addElement(new Zone("Zone3"));
12
13     buttons.addElement("Mode");
14     buttons.addElement("Set");
15     buttons.addElement("Up");
16     buttons.addElement("Down");
17   }
18 }

Listing 2. The generated code for the Stopwatch application

As can be seen above, the code for the display definition is straightforward and simple: a new concrete display class inherits from AbstractDisplay, and the required user interface components are defined within the class constructor method.

Having covered the modeling language and the code generator, we will next discuss issues related to the domain framework code.

Up Previous Next Title Page Contents