| EAA-dev Home |

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

An Introductory Example

Last significant update: 06 Aug 07

Contents


Picking up this book, you may be asking yourself such questions as "what is a DSL", "how does it fit in with my usual development work" and "why would I find such a thing useful"? This chapter begins by looking at these questions. I'll talk about what a DSL is, the various types of DSL, the reasons to use a DSL, and the broader topic of language processing. At this stage I won't talk about how the various styles of DSL work - we'll get into that later.


The example

As is usual for me, I'll base this discussion on an example, as I find that a concrete example usually makes it easier to follow an abstract topic like this.

Let's imagine a company that makes systems to control access to secret compartments. Their customers are people who are bored with numeric keypads and security codes, instead preferring something recalling bad movies set in gothic castles.

So Miss Grant has a secret compartment in her bedroom that is normally locked and concealed. To open it she has to close the door, open the second draw in her chest, turn her bedside light on - and then the secret panel is unlocked for her to open. Mr G has the same basic system for a panel in his bedroom. He has to open his bathroom door and turn on the main light which allows him to open the panel. The panel reveals a safe, but it has an overriding lock that cannot be opened unless he turns his light off and on again. Mr C has a compartment in his office, he has to close his main door, take a picture off the wall, turn his desklight on three times, open the top draw of his filing cabinet, turn the desklight off - and then the panel's unlocked. If he forgets to turn the desklight off, he wants an alarm to sound.

Although this example is deliberately whimsical, the underlying point isn't that unusual. What we have is a family of systems which share most components and behaviors, but have some important differences. In this case we have some kind of controller software which communicates with various devices around the room. The variability is in the sequence of actions that can be carried out and the actions that the software does as a result of these sequences. We want to arrange things so that the company can install a new system with the minimum of effort, so it must be easy for them to program the sequence of actions into the controller.

The software they use is written in java, as it has to run on a job lot of toasters they picked up from a failed dot com. Since they can run java, they can program their controllers in any way they like. Looking at their many customers they realize there is a common theme to the way they behave. The various devices send events as they are manipulated. The system reacts differently to events depending on the recent sequence of events. This style of thinking about behavior is that of a state machine. Thus it makes sense to build a small state machine framework, this way it's quicker for them to understand and build new controllers for each site.

The State Machine Framework

There are many varieties of state machine models around, this one is simple, and with a little twist for the this particular case. We could use a general state machine model, but we can do with something much simpler that's more suited to this particular domain. By doing this we give up some power for an easier job working with it. We also give up the ability to use an existing framework. Neither of these points is really part of the DSL discussion so we won't explore these trade-offs further.

To help understand how it works, let's take a Miss Grant's system.

Figure 1

Figure 1: State diagram for Miss Grant

[TBD: Add reset event association to class diagram]

The system has a controller that receives events and commands from the various devices scattered around. Each event and command has a four letter code that's the actual signal sent through the communication channels. We want to refer to these in the controller code with symbolic names, so we create event and command classes with a code and a name. We keep them as separate classes (with a superclass) as they play different roles in our controller code.

class AbstractEvent...
  private String name, code;

  public AbstractEvent(String name, String code) {
    this.name = name;
    this.code = code;
  }
  public String getCode() { return code;}
  public String getName() { return name;}
public class Command extends AbstractEvent
public class Event extends AbstractEvent

Figure 2

Figure 2: Class diagram of the state machine framework

The key to the structure is that state class. Each state class keeps track of the events and commands.

class State...
  private String name;
  private List<Command> actions = new ArrayList<Command>();
  private Map<String, Transition> transitions = new HashMap<String, Transition>();

  public void addTransition(Event event, State targetState) {
    transitions.put(event.getCode(), new Transition(this, event, targetState));
  }
class Transition...
  private final State source, target;
  private final Event trigger;

  public Transition(State source, Event trigger, State target) {
    this.source = source;
    this.target = target;
    this.trigger = trigger;
  }

  public State getSource() {return source;}

  public State getTarget() {return target;}

  public Event getTrigger() {return trigger;}

  public String getEventCode() {return trigger.getCode();}

The state machine holds on to its start state.

class StateMachine...
  private State start;

  public StateMachine(State start) {
    this.start = start;
  }

Any other states in the machine are then those that are reachable from this state.

class StateMachine...
  public Collection<State> getStates() {
    List<State> result = new ArrayList<State>();
    gatherForwards(result, start);
    return result;
  }

  private void gatherForwards(Collection<State> result, State start) {
    if (start == null) return;
    if (result.contains(start)) return;
    else {
      result.add(start);
      for (State next : start.getAllTargets()) {
        gatherForwards(result, next);
      }
      return;
    }
  }
class State...
  Collection<State> getAllTargets() {
    List<State> result = new ArrayList<State>();
    for (Transition t : transitions.values()) result.add(t.getTarget());
    return result;
  }

There is one particular wrinkle to this problem. These controllers have a particular nature to them in that most of the time they are in their start state, which is effectively an idle state. There are events that advance the state machine, and there are events that take you back to that start state. For this case opening the door always takes you back to the start. So I let the machine keep track of reset events.

class StateMachine...
  private List<Event> resetEvents = new ArrayList<Event>();

  public void addResetEvents(Event... events) {
    for (Event e : events) resetEvents.add(e);
  }

I don't need to have a separate structure for reset events here. I could handle this by simply declaring extra transitions on the state machine like this

class StateMachine...
  private void addResetEvent_byAddingTransitions(Event e) {
    for (State s : getStates())
      if (!s.hasTransition(e.getCode())) s.addTransition(e, start);
  }

I prefer explicit reset events on the machine becuase that better expresses the intention of what I'm trying to do. While it does complicate the machine a bit, it keeps the clarity of my intention of how a general machine is supposed to work, as well as keeping the intention of how a particular machine is defined.

With the structure out of the way, now lets move on to the behavior. As it turns out, it's really quite simple. The controller has a handle method that takes the event code it receives from the device.

class Controller...
  private State currentState;
  private StateMachine machine;

  public CommandChannel getCommandChannel() {
    return commandsChannel;
  }

  protected CommandChannel commandsChannel;

  public void handle(String eventCode) {
    if (currentState.hasTransition(eventCode))
      transitionTo(currentState.targetState(eventCode));
    else if (machine.isResetEvent(eventCode))
       transitionTo(machine.getStart());
     // ignore unknown events
  }
  private void transitionTo(State target) {
    currentState = target;
    currentState.executeActions(commandsChannel);
  }
class State...
  public boolean hasTransition(String eventCode) {
    return transitions.containsKey(eventCode);
  }
  public State targetState(String eventCode) {
    return transitions.get(eventCode).getTarget();
  }
  public void executeActions(CommandChannel commandsChannel) {
    for (Command c : actions) commandsChannel.send(c.getCode());
  }
class StateMachine...
  public boolean isResetEvent(String eventCode) {
    return resetEventCodes().contains(eventCode);
  }

  private List<String> resetEventCodes() {
    List<String> result = new ArrayList<String>();
    for (Event e : resetEvents) result.add(e.getCode());
    return result;
  }

It ignores any events that are not registered on the state. For any events that are recognized, it transitions to the target state and executes any commands defined on that target state.


Programming the Controller with a Domain Specific Language

With this framework in place, we can now program Miss Grant's controller like this.

    Event doorClosed = new Event("doorClosed", "D1CL");
    Event drawOpened = new Event("drawOpened", "D2OP");
    Event lightOn = new Event("lightOn", "L1ON");
    Event doorOpened = new Event("doorOpened", "D1OP");
    Event panelClosed = new Event("panelClosed", "PNCL");

    Command unlockPanelCmd = new Command("unlockPanel", "PNUL");
    Command lockPanelCmd = new Command("lockPanel", "PNLK");
    Command lockDoorCmd = new Command("lockDoor", "D1LK");
    Command unlockDoorCmd = new Command("unlockDoor", "D1UL");

    State idle = new State("idle");
    State activeState = new State("active");
    State waitingForLightState = new State("waitingForLight");
    State waitingForDrawState = new State("waitingForDraw");
    State unlockedPanelState = new State("unlockedPanel");

    StateMachine machine = new StateMachine(idle);

    idle.addTransition(doorClosed, activeState);
    idle.addAction(unlockDoorCmd);
    idle.addAction(lockPanelCmd);

    activeState.addTransition(drawOpened, waitingForLightState);
    activeState.addTransition(lightOn, waitingForDrawState);

    waitingForLightState.addTransition(lightOn, unlockedPanelState);

    waitingForDrawState.addTransition(drawOpened, unlockedPanelState);

    unlockedPanelState.addAction(unlockPanelCmd);
    unlockedPanelState.addAction(lockDoorCmd);
    unlockedPanelState.addTransition(panelClosed, idle);

    machine.addResetEvents(doorOpened);

I look at this last bit of code as quite different in nature to the previous peices. The earlier code described how to build the state machine framework, this last bit of cod is about how to use that framework for one particular controller. You often see divisions like this. On the one hand is library, frameowork, or component implementation code; on the other is configuration or component assembly code. Essentially it is the separation of common code from variable code. We structure the common code in a set of components that we then configure for multiple different purposes.

Here is another way of representing that configuration code.

<stateMachine start = "idle">
    <event name="doorClosed" code="D1CL"/>
    <event name="drawOpened" code="D2OP"/>
    <event name="lightOn" code="L1ON"/>
    <event name="doorOpened" code="D1OP"/>
    <event name="panelClosed" code="PNCL"/>

    <command name="unlockPanel" code="PNUL"/>
    <command name="lockPanel" code="PNLK"/>
    <command name="lockDoor" code="D1LK"/>
    <command name="unlockDoor" code="D1UL"/>

  <state name="idle">
    <transition event="doorClosed" target="active"/>
    <action command="unlockDoor"/>
    <action command="lockPanel"/>
  </state>

  <state name="active">
    <transition event="drawOpened" target="waitingForLight"/>
    <transition event="lightOn" target="waitingForDraw"/>
  </state>

  <state name="waitingForLight">
    <transition event="lightOn" target="unlockedPanel"/>
  </state>

  <state name="waitingForDraw">
    <transition event="drawOpened" target="unlockedPanel"/>
  </state>

  <state name="unlockedPanel">
    <action command="unlockPanel"/>
    <action command="lockDoor"/>    
    <transition event="panelClosed" target="idle"/>
   </state>

  <resetEvent name = "doorOpened"/>
</stateMachine>

This style of representation should look familiar to most readers, I've expressed it as an XML file. There are several advantages to doing it this way. One obvious reason is that now we don't have to compile a separate java program for each controller we put into the field - instead we can just compile the state machine components plus an appropritate parser into a common jar, and ship the xml file to be read when the machine starts up. Any changes to the behavior of the controller can be done without having to distribute a new jar. (We do, of course, pay for this in that any mistakes in the syntax of the configuration can only be detected at run time.)

A second advantage is in the expressiveness of the file itself. We no longer need to worry about the details of making the various connections through variables. Instead we have a more declarative approach that in many ways reads much more clearly. We're also limited in that we can only express configuration in this file - limitations like this often are helpful because they can reduce the chances for people making mistakes in the component assembly code.

These advantages are why so many frameworks in Java and C# are configured with XML configuration files. These days it sometimes feels that you're doing more programming with XML than you are with main programming language.

Here's another version of the configuration code.

events
  doorClosed  D1CL
  drawOpened  D2OP
  lightOn     L1ON
  doorOpened  D1OP
  panelClosed PNCL
end

resetEvents
  doorOpened
end

commands
  unlockPanel PNUL
  lockPanel   PNLK
  lockDoor    D1LK
  unlockDoor  D1UL
end

state idle
  actions {unlockDoor lockPanel}
  doorClosed => active
end

state active
  drawOpened => waitingForLight
  lightOn    => waitingForDraw
end

state waitingForLight
  lightOn => unlockedPanel
end

state waitingForDraw
  drawOpened => unlockedPanel
end

state unlockedPanel
  actions {unlockPanel lockDoor}
  panelClosed => idle
end

This is code, although not in a syntax that's familiar to you. In fact it's a custom syntax that I made up for this example. I think it's a syntax that's easier to write, and above all read, than the XML syntax. It's terser and avoids a lot of the quoting and noise characters that the XML suffers from. You probably wouldn't have done it exactly the same way, but the point is that you can construct whatever syntax you and your team prefers. You can still load it in at runtime (like the XML) but you don't have to (as you don't with the XML) if you want it at compile time.

This language is a Domain Specific Language, and shares many of the characteristics of DSLs. Firstly it's suitable only for a very narrow purpose - it can't do anything other than configure this particular kind of state machine. As a result the DSL is very simple - there's no facility for control structures or anything else. It's not even Turing complete. You couldn't write a whole application in this language - all you can do is describe one small aspect of an application. As a result the DSL has to be combined with other languages to get anything done. But the simplicity of the DSL means it's easy to edit and translate.

Now look again at the XML representation. Is this a DSL? I would argue that it is. It's in an XML syntax - but it's still a DSL. This example thus raises a design issue - is it better to have custom concrete syntax for a DSL or an XML concrete syntax? The XML syntax can be easier to parse since people are so familiar with parsing XML. Actually in this case the custom syntax took me about the same amount of time using a parser generator. I'd contend that the custom syntax is much easier to read, at least in this case. But however you view this choice the core trade-offs around DSLs are the same. Indeed you can argue that most XML configuration files are essentially DSLs.

Let's go back a step further, back to the configuration code in Java - is this a DSL?

While you're thinking of that look at this code. Does this look like a DSL for this problem?

event :doorClosed, "D1CL"
event :drawOpened,  "D2OP"
event :lightOn, "L1ON"
event :doorOpened,  "D1OP"
event :panelClosed, "PNCL"

command  :unlockPanel, "PNUL"
command  :lockPanel,   "PNLK"
command  :lockDoor,    "D1LK"
command  :unlockDoor,  "D1UL"

resetEvents :doorOpened

state :idle do
  actions :unlockDoor, :lockPanel
  transitions :doorClosed => :active
end

state :active do
  transitions :drawOpened => :waitingForLight,
              :lightOn => :waitingForDraw
end

state :waitingForLight do
  transitions :lightOn => :unlockedPanel
end

state :waitingForDraw do
  transitions :drawOpened => :unlockedPanel
end

state :unlockedPanel do
  actions :unlockPanel, :lockDoor
  transitions :panelClosed => :idle
end

It's a bit noisier than the custom language earlier, but still pretty clear. Readers who have similar language likings to me will probably know that it's Ruby. Ruby gives me a lot of syntactic options that makes for more readable code, so I can make it look very similar to the custom language.

Ruby developers would consider this code to be a DSL. I use a subset of the capabilities of Ruby and capture same ideas as our XML and custom syntax. Essentially I'm embedding the DSL into ruby, using a subset of ruby as my syntax. To an extent this is more a matter of attitude than of anything else. I'm choosing to look at the Ruby code through DSL glasses. But it's a point of view with a long tradition - Lisp programmers often think of creating DSLs inside Lisp.

This brings me to pointing out that there are two kinds of textual DSLs: which I call external and internal DSLs. An External DSL is a domain specific language represented in a separate language to the main programming language it's working with. This language may be a custom syntax, or it may follow the syntax of another representation (like XML). An Internal DSL is DSL expressed within the syntax of a general purpose language. It's a stylized use of that language for a domain specific purpose.

You may also hear the term embedded DSL as a synonym for internal DSL. Although it is fairly widely used, I avoid this term because you also hear "embedded language" applied to scripting languages embedded within applications: such as VBA in Excel or Scheme in the Gimp. So I use internal DSL to avoid confusion.

Now think again about the original java configuration code - is this a DSL? I would argue that it isn't. That code feels like stitching together with an API, while the ruby code above has more the feel of a declarative language. Does this mean you can't do an internal DSL in Java? How about this?

public class BasicStateMachine extends StateMachineBuilder {

  Events doorClosed, drawOpened, lightOn, panelClosed;
  Commands unlockPanel, lockPanel, lockDoor, unlockDoor;
  States idle, active, waitingForLight, waitingForDraw, unlockedPanel;
  ResetEvents doorOpened;

  protected void defineStateMachine() {
    doorClosed. code("D1CL");
    drawOpened. code("D2OP");
    lightOn.    code("L1ON");
    panelClosed.code("PNCL");

    doorOpened. code("D1OP");

    unlockPanel.code("PNUL");
    lockPanel.  code("PNLK");
    lockDoor.   code("D1LK");
    unlockDoor. code("D1UL");

    idle
        .actions(unlockDoor, lockPanel)
        .transition(doorClosed).to(active)
        ;

    active
        .transition(drawOpened).to(waitingForLight)
        .transition(lightOn).   to(waitingForDraw)
        ;

    waitingForLight
        .transition(lightOn).to(unlockedPanel)
        ;

    waitingForDraw
        .transition(drawOpened).to(unlockedPanel)
        ;

    unlockedPanel
        .actions(unlockPanel, lockDoor)
        .transition(panelClosed).to(idle)
        ;
 }
}

It's formatted oddly, and uses some unusual programming conventions, but it is valid Java. It's java written in what is these days called a Fluent Interface style. A Fluent Interface is an API that's designed to read like an internal DSL. This I would call a DSL - although it's more messy than the ruby DSL it still has that declarative flow that a DSL needs.

What makes a fluent interface different to a normal API? This is a tough question that I'll spend more time on later), but it comes down to a rather fuzzy notion of a language-like flow. Given this distinction it's useful to have a name for a non-fluent API - I'll use the term push-button API, for reasons I'll expand on later.


Output Production

The various code fragments above illustrated some of the forms that some DSL code can take, but what does that DSL code produce? In the example, it populated the state machine framework. So we might imagine that when we start up Miss Grant's controller, the software system reads the DSL script, populates the state machine framework and then runs using the state machine objects to control the system's behavior.

The state machine framework plays a very central role in this design. In the way I've described it, the state-machine comes first and the DSL is layered over it to make it easier and more flexible to configure. This is a common and useful way in which DSLs are formed.

However there is another direction, one where we begin with the language itself. In this kind of situation we might begin by thinking that we want to control the security systems with a state-machine DSL language. In this situation we should still build the state-machine framework, since this acts as our fundamental description of how our controller should work. The state-machine framework is the Semantic Model of the DSL.

When people describe building DSLs, particularly with external DSLs, they often don't put much emphasis on the Semantic Model. In this book, however, I consider the Semantic Model to play a central role. There are times when you don't need a Semantic Model, but these are quite rare. Using a Semantic Model is my default option with a DSL. You can have a Semantic Model beforehand as an existing framework, or you can build the Semantic Model as you build the DSL. Either way the Semantic Model is a central part of the picture.

In this case, we process the DSL by populating the Semantic Model and then execute the Semantic Model to provide the behavior that we want from the controller. This approach is what's known in language circles as interpretation. When we interpret some text, we parse it and immediately produce the result that we want from the program. (Interpret is a tricky word in software circles, since it carries all sorts of connotations for people, however I'll use it strictly to mean this form of immediate execution.)

In the language world, the alternative to interpretation is compilation. With compilation, we parse some program text and produce an intermediate output, which is then separately processed to provide the behavior we desire. In the context of DSLs the compilation approach is usually referred to as code-generation. In this case this might mean generating some java code to represent the particular behavior of Miss Grant's controller.

Code generation is often awkward in that it often pushes you to an extra compilation. To build your program you have to first compile the state framework and the parser, then run the parser to generate Miss Grant's controller, then compile the generated code for Mrs H's controller and then finally you have the controller that you can execute. This makes your build process much more complicated.

However an advantage of code generation is that there's no reason why you have to generate code in same programming language that you use for the parser. In this case you can avoid the second compilation step by generating code for a dynamic language such as javascript or jruby. Code generation is also useful when you want to use DSLs with a language platform that doesn't have the tools for DSL support. I've come across recent projects that generate code for MathCAD, SQL, and COBOL.

Many writings on DSLs focus on code-generation, even to the point of making code-generation the primary aim of the exercise. As a result you can find articles and books extolling the virtues of code-generation. In my view, however, code-generation is merely an implementation mechanism, one that isn't actually needed in most cases. Certainly there are times when you must use code-generation, but there are also plenty of times when you don't need it.


Using Language Workbenches

The two styles of DSL I've shown so far (internal and external) are the traditional ways of thinking about DSLs. They may not be as widely understood and used as they should be, but they have a long history and moderately wide usage. There is a third category of DSL tool that I'm going to talk about in this book: Language Workbenches. These are a relatively recent approach and not so widely used. Furthermore I think it will be a few years before most readers of this book will start thinking about any kind of serious usage of them. However I include them because I think they are a very interesting step for DSLs and have the potential to be usher in a significant shift in software development. They also generate a fair amount of chatter, so it's important to have at least an overview of what they are and how they relate to the more traditional techniques.

To start with, take a look at Figure 3. At first glance this looks rather like another external DSL using a custom syntax - albeit with a fancy editor to work on it. As it turns out this surface similarity hides deep differences between this and what we've seen before.

Figure 3

Figure 3: This will be a screenshot of MPS editing a representation of Miss Grant's contoller.[TBD: insert figure]

For someone editing a DSL program, the most obvious change is in the editing experience. Rather than thinking of the program as a stream of characters, the editor feels like a collection of nested tables where each cell takes a particular kind of content, tied into the overall model of the state machine. As a result you spend less time typing and more time using autocompletion. It's rather like using post-intellij IDE's but taken even further.

Behind this significant surface difference there is an even greater difference behind the covers, one that alters the entire traditional compilation process.

Let's look at what happens when we compile a C program. The core representation of the program is a text file. We run a compiler on this text file to turn it into an executable that we can run on our particular machine. The compiler works (at least when seen from the top of Mount Everest) by parsing the source into an internal and transient abstract representation (often referred to as an Abstract Syntax Tree or AST). A code generation step then navigates this abstract representaiton and generates the machine code.

Figure 4

Figure 4: An outline of translating a source-driven language[TBD: alter names to match language mentioned in text.]

There are lots of variations on this, but they all follow the basic sequence of parse and code generate. A python program is interpreted - which means that the same parse and production of machine code occurs, but all at runtime and the machine code isn't persisted. A java compiler generates byte-code that's interpreted by the JVM. Both internal and external DSLs are source-driven and thus follow this kind of sequence.

[TBD: Put something here on general difference between source and repository based systems.]

A language workbench takes a very different style of approach. Instead of centering on the source code, it focuses instead on the abstract representation. This is has its own life and is persistent. In order to edit the program the workbench projects the abstract representation into an editable form. This editable representation is transient, only existing while you are working with it. In Figure 4 what you see is the interal abstract representation of the state machine being projected into this nested tabular structure. When you click in a cell the workbench uses the abstract representation and the projection rules to guide you with autocompletion.

Figure 5

Figure 5: Language Workbenches are centered around the abstract representation.

This approach is similar to what you see in post-intelliJ IDEs. In that case the IDE generates an AST of the program you are editing and uses that information to drive the autocompletion. At times it feels that you're not typing, instead you are just driving shortcuts against the IDE. The difference is that these IDEs are still source driven, creating the AST when they start up. With Language Workbenches it's the other way around - the editable representation is the one that's created when you open an editor. This projectional editor is a transient representation of some of the abstract representation into a form that's convenient to edit.

A consequence of projectional editors is that you can have multiple projections for the same part of the abstract representation. This allows you to project the same information in different forms, or to project different subsets of information depending on how you want to use it. A good example of the former is a common demonstration of Intentional Software's language workbench where a conditional expression can be projected in C-like syntax, lisp-like syntax, or a tabular form.

The consequence of all this is that language workbenches give you a much more sophisticated tool experience when working with DSLs - and just as important a sophisticated tool for creating them. Language Workbenches provide tools to define the structure of a DSL and its editors.

The example in Figure 4 showed a language workbench with a broadly textual projection, but that's not the only flavor of language workbench. Another class of langauge workbenches prefer a diagrammatic projection.

[TBD: Update metaedit example to add panelClosed -> idle transition]

Figure 6

Figure 6: Miss Grant's controller edited using metaedit - a graphical language workbench.

[TBD: Put a screenshot of Microsoft's LW here]

Figure 4 shows Miss Grant's controller edited in a diagramatic form. All the elements of the diagram can be defined and edited in the workbench. Not just is this a matter of customizing the icons, it's also specifying the rules that link them together. As as common in these tools we see three views of the model - a hierachtic browser, a main pane with the bulk of this DSL program, and a properties window that allows us to see less vital parts of the model.

There isn't any fundamental reason why language workbenches need to be either graphical or textual. As I write this they do follow that split, but this is more an indication of their history than anything else. Textual tools tend to come from people with a background in compiler technology, graphical tools tend to come from people with more background in CASE tools and Model Driven Development. The communities are currently quite separate.

[TBD: Mention using things like Excel as a syntax.]

Visualization

One the great advantages of using a Language Workbench is that this enables you to a wider range of representations of the DSL, in particular graphical representations. However even with a textual DSL you can obtain a diagrammatic representation. Indeed we saw this very early on in this chapter. When looking at Figure 1 it might have struck you that the diagram was not as neatly drawn as I usually do. The reason for this is that I didn't draw the diagram, I generated it automatically from the Semantic Model of Miss Grant's controller. Not just do my state machine classes execute, they also are able to render themselves use the dot language.

The dot langauge is part of the GraphViz package, which is an open-source tool that allows you to describe mathematical graph structures (nodes and edges) and then automatically plot them. It figures out how to lay out the graph, you just tell it what the nodes and edges are, what shapes to use, and some other hints.

Using a tool like GraphViz is extremely helpful for many kinds of DSLs because it gives another representation. This visualization representation is similar to the DSL itself in that it allows a human to understand the model. The diference between a visualization and the source is that it isn't editable - however it can provide options that are too hard in an editable form, such as a diagram like this.

In the terms of a language workbench you can think of a visualization as a read-only projection. It's something that can be less important for graphical language workbenches, since you use a diagram anyway, but it's still sometimes a handy technique.

Visualizations don't have to be graphical. I often use a simple textual visualization to help me debug while I'm writing a parser. I've seen people generate visualizations in Excel to help communicate with domain experts. The point is that once you have done the hard work of creating a component framework like this, adding visualizations is really easy. You'll note here that the visualizations are produced from the framework, not the DSL, so you can do this even if you aren't using a DSL to populate the framework.

Indeed the techniques in this book can be used for creating visualizations above and beyond DSL usage. A partial parser for a general purpose language can be used to visualize useful aspects of a general purpose program. Any interesting data strucutre can be visualized in interesting ways.


Significant Revisions

06 Aug 07: First Draft

09 Apr 08: Split example from general issues