| EAA-dev Home |

WORK-IN-PROGRESS: - this material is still under development

Transformer Generation

Generate code by writing a transformer that navigates the input model and produces output.

How it Works

Transformer Generation involves writing a program that takes the Semantic Model as input and produces an output in the form of source code in the target environment. With transformers I like of them in terms of input-driven and output-driven sections. Output-driven transformation starts from the required output and dives into the input to gather the data it needs as it goes. Input-driven transformation walks the input data structure and produces output.

For an example, consider generating a web page based on a catalog of products. An output driven approach would start with the structure of the web page, perhaps with a routine like

renderHeader();
renderBody();
renderFooter();

An input driven transformation looks instead at the input data structure and navigates through that, perhaps like this.

foreach (prod in products) {
  renderName(prod);
  foreach (photo in prod.photos) {
	  renderPhoto(photo);
  }
}

Often transformers use a combination of the two. I seem to regularly run into situations where the outer logic is output driven, but then calls routines that are more input driven. The outer logic describes the broad structure of the output document, dividing it into logical sections, while the input section produces output driven by a particular kind of input data. In any case I find it useful to think of each routine in the transformation as either input or output driven and to be conscious of which I'm using.

Many transformations can go directly from Semantic Model to target source, but for more complicated transforms it can be useful to break down the transformation into multiple steps. A two step transform, for instance, would walk the input model and produce an output model. This output model would be model, rather than text, but more oriented towards the generated output. A second step might then walk the output model and produce the output text. Using a multi-step transform is useful when the transform is complicated, or if you have multiple output texts to produce that share some characteristics from the input. With multiple output texts you can produce a single output model with the common elements in the first stage transform. The difference between the output can then be placed in varying second stages.

With a multi-stage approach you can also mix techniques in the stages, using Transformer Generation for the first stage and Templated Generation for the second stage.

When to use it

A single stage Transformer Generation is a good choice when the output text has a simple relationship with the input model and most of the output text is generated. In this case Transformer Generation is very easy to write and doesn't require introducing a templating tool.

Transformer Generation with multiple stages can be very useful when the relationship between input and output is more complex as each stage can handle a different aspect of the problem.

If you use Model-Aware Generation you can usually populate the model with a simple sequence of calls, which is usually easy to generate with Transformer Generation.

Example: Secret Panel Controller (Java generating C)

Using Model-Aware Generation often goes with Transformer Generation as the separation between generated code and static code is clear allowing any sections of generated code to have very little static code. So in this case I'll generate the code for the secret panel controller from the example in Model-Aware Generation. To save you flipping pages, here is the code I need to generate.

#include "sm.h"
#include "sm-pop.h"

void build_machine() {
  declare_event("doorClosed", "D1CL");
  declare_event("drawOpened", "D2OP");
  declare_event("lightOn", "L1ON");
  declare_event("doorOpened", "D1OP");
  declare_event("panelClosed", "PNCL");

  declare_command("lockDoor", "D1LK");
  declare_command("lockPanel", "PNLK");
  declare_command("unlockPanel", "PNUL");
  declare_command("unlockDoor", "D1UL");

  declare_state("idle");
  declare_state("active");
  declare_state("waitingForDraw");
  declare_state("unlockedPanel");
  declare_state("waitingForLight");

  /* body for idle state */
  declare_action("idle", "unlockDoor");
  declare_action("idle", "lockPanel");
  declare_transition("idle", "doorClosed", "active");

  /* body for active state */
  declare_transition("active", "lightOn", "waitingForDraw");
  declare_transition("active", "drawOpened", "waitingForLight");

  /* body for waitingForDraw state */
  declare_transition("waitingForDraw", "drawOpened", "unlockedPanel");

  /* body for unlockedPanel state */
  declare_action("unlockedPanel", "unlockPanel");
  declare_action("unlockedPanel", "lockDoor");
  declare_transition("unlockedPanel", "panelClosed", "idle");

  /* body for waitingForLight state */
  declare_transition("waitingForLight", "lightOn", "unlockedPanel");

  /* reset event transitions */
  declare_transition("idle", "doorOpened", "idle");
  declare_transition("active", "doorOpened", "idle");
  declare_transition("waitingForDraw", "doorOpened", "idle");
  declare_transition("unlockedPanel", "doorOpened", "idle");
  declare_transition("waitingForLight", "doorOpened", "idle");
}

The output that I need to generate has a very simple structure, which I can show by how I construct the outer routine of the generator.

class StaticC_Generator...
  public void generate(Writer output) throws IOException {
      output.write(header);
      generateEvents(output);
      generateCommands(output);
      generateStateDeclarations(output);
      generateStateBodies(output);
      generateResetEvents(output);
      output.write(footer);
  }

This code is a typical output-driven outer routine of a transformer. I might as well go through each of these steps in the order they come. The header just writes out the static stuff I need at the top of the file.

class StaticC_Generator...
  private static final String header =
      "#include \"sm.h\"\n" +
      "#include \"sm-pop.h\"\n" +
      "\nvoid build_machine() {\n";

When I create the generator, I create it with the state machine that it will work on.

class StaticC_Generator...
  private StateMachine machine;
  public StaticC_Generator(StateMachine machine) {
    this.machine = machine;
  }

The first time I use this is to generate the event declarations

class StaticC_Generator...
  private void generateEvents(Writer output) throws IOException {
    for (Event e : machine.getEvents())
      output.write(String.format("  declare_event(\"%s\", \"%s\");\n", e.getName(), e.getCode()));
    output.write("\n");
  }

The commands and state declarations are similarly easy.

class StaticC_Generator...
  private void generateCommands(Writer output) throws IOException {
    for (Command c : machine.getCommands())
      output.write(String.format("  declare_command(\"%s\", \"%s\");\n", c.getName(), c.getCode()));
    output.write("\n");
  }

  private void generateStateDeclarations(Writer output)throws IOException {
     for (State s : machine.getStates())
       output.write(String.format("  declare_state(\"%s\");\n", s.getName()));
    output.write("\n");
  }

Next I generate the body (actions and transitions) for each state. In this case I have to declare all the states before I can declare transitions since I'll get an error if I forward reference a state.

class StaticC_Generator...
  private void generateStateBodies(Writer output) throws IOException {
    for (State s : machine.getStates()) {
      output.write(String.format("  /* body for %s state */\n", s.getName()));
      for (Command c : s.getCommands()) {
         output.write(String.format("  declare_action(\"%s\", \"%s\");\n", s.getName(), c.getName()));
      }
      for (Transition t : s.getTransitions()) {
          output.write(String.format(
              "  declare_transition(\"%s\", \"%s\", \"%s\");\n",
              t.getSource().getName(),
              t.getTrigger().getName(),
              t.getTarget().getName()));
      }
      output.write("\n");
    }
  }

This also demonstrates a flip into an input-driven style. The code that's generated in each case follows the structure of the input model. This is fine as it doesn't matter in which order I declare actions and transitions. This code also shows me generating a comment with dynamic data.

Finally I generate the reset events.

class StaticC_Generator...
  private void generateResetEvents(Writer output) throws IOException {
    output.write("  /* reset event transitions */\n");
    for (Event e : machine.getResetEvents())
      for (State s : machine.getStates())
        if (!s.hasTransition(e.getCode())) {
          output.write(String.format(
              "  declare_transition(\"%s\", \"%s\", \"%s\");\n",
              s.getName(),
              e.getName(),
              machine.getStart().getName()));
        }
  }

Significant Revisions

09 Jun 08: First draft