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+ report 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 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 the code generator has been implemented, then? As we have seen before, the code generators in MetaEdit+ are defined with a dedicated report definition language. Each report definition is associated with certain graph type and thus can operate on models made according to that specific graph type. These report definitions – that could be also referred to as sub-generators – can be arranged in hierarchical fashion. The top level of the code generator architecture of the watch example (i.e. the sub-generators associated with WatchFamily graph type) is presented in Figure 3-3 (a ‘*’ in the name of a sub-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 the ‘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 ‘_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 need like HTML for 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 master 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 model specific user interface components 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 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 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 that presents 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 Stopwatch application

For the comprehensive 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 provides also 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 used also 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 the same way. As there are typically no actions related to the state transitions within a logical watch, they are left off from the generated code as well. More over, in order to support the references to the lower-level state machines (i.e. to the watch applications the logical watch is made of), the definitions of these decomposition structures must be generated. This is taken care 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.

Up Previous Next Title Page Contents