Fowler’s DSL example with MGrammar (Draft!)

I drafted Fowlers state-machine example with MGrammar. While doing so I found a few things I would like to see optimized or find better solutions for.

  • New line handling. I would like to express one required new line, when it is required, but then ignore all the others. Forum discussion.
    I could make new lines part of some tokens and then just interleave other tokens. But I don’t think this would be a nice solution. Any ideas?
  • The blocks events, resetEvents, commands and state+ have to be written in order. It is possible to describe a grammar where unordered blocks would be allowed, but it’s not trivial.
    Ordered and unordered “Lists” of syntaxes, separated somehow should IMHO be supported directly: forum discussion.
  • I wrote the Grammar using Intellipad and the three-pane input/grammar/AST view. It’s great! But I’d like to define the Semantic Model (using MSchema) along with the grammar and directly validate my AST against it: forum discussion.
  • References are not directly supported by MGrammar/MSchema. Forum discussion.
  • Xtext, which is quite comparable to MGrammar has a much more compact syntax. But I haven’t done a more deep comparison yet.
    Sven Efftinge’s Blog: Fowler’s DSL example with Xtext

Still missing:

  • A schema that represents the structure of the AST and validates it using constraints.
  • An adapter to Simple State Machine on CodePlex or a generating a state machine framework using T4.

Would love your feedback…

Grammar definition

module MGrammar.Net.Sample {

  // This is my first try, to parse the state machine
  // dsl syntax proposed by Martin Fowler
  language Statemachine 
  {
    interleave Ignore = " ";
    
    syntax Main = 
        NewLines? e:Block(Events, NameAndCode)
        NewLines+ r:Block(ResetEvents, Name)
        NewLines+ c:Block(Commands, NameAndCode)
        NewLines+ s:List(StateBlock)
        NewLines?
        => Statemachine 
        {
          Events { valuesof(e) },
          ResetEvents { valuesof(r) },
          Commands { valuesof(c) },
          valuesof(s)
        };
        
    // list of syntax "Item" separated by one or more
    // new lines
    syntax List(Item) = n:Item => {n}
        | list:List(Item) NewLines+ n:Item 
            => {valuesof(list), n};
    
    // lists of tokens separated by whitespaces 
    syntax TokenList(Item) = n:Item => {n}
        | list:List(Item) n:Item 
            => {valuesof(list), n};

    // a block consisting of a keyword, new-line-
    // separated "Item"s and end
    syntax Block(Keyword, Item) = Keyword 
            NewLines items:List(Item) 
            NewLines End => {valuesof(items)};
            
    // a word projected as a name
    // grouped into a separate successor
    // for extensibility reasons
    syntax Name = n:Word
        => { Name {n} };
        
    // a word plus a code, separated by spaces
    syntax NameAndCode = n:Word c:Word
        => {Name {n}, Code{c}};
    
    
    // special block and subsyntaxes for states
    syntax StateBlock = State n:Word 
            actions:(NewLines a:Actions => a)?
            NewLines+ items:List(Transition) 
            NewLines+ End 
            => State 
            {
                Name { n }, 
                actions, 
                Transitions { valuesof(items) } 
            };

    syntax Transition = e:Word "=>" s:Word
        => { Event {e}, ToState{s} };
        
    syntax Actions = tActions "{" l:TokenList(Word) "}" 
        => Actions [ valuesof(l) ];
    
    
    // keyword specification for strong formatting
    @{Classification["Keyword"]} final token Events = "events";
    @{Classification["Keyword"]} final token Commands = "commands";
    @{Classification["Keyword"]} final token ResetEvents = "resetEvents";
    @{Classification["Keyword"]} final token State = "state";
    @{Classification["Keyword"]} final token End = "end";
    @{Classification["Keyword"]} final token tActions = "actions";
    
    
    // Some whitespace characters
    token NewLineCharacter 
            = 'u000A'  // New Line
            | 'u000D'  // Carriage Return
            | 'u0085'  // Next Line
            | 'u2028'  // Line Separator
            | 'u2029'; // Paragraph Separator
            
    token NewLines = NewLineCharacter#2..;
         
    token Letter = 'a'..'z' | 'A'..'Z';
    token Digit = '0'..'9';
    token Word = (Letter (Letter | Digit)*);
  }
}

Input Text

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

Output AST

Statemachine{
  Events{
    {
      Name{"doorClosed"},
      Code{"D1CL"}
    },
    {
      Name{"drawOpened"},
      Code{"D2OP"}
    },
    {
      Name{"lightOn"},
      Code{"L1ON"}
    },
    {
      Name{"doorOpened"},
      Code{"D1OP"}
    },
    {
      Name{"panelClosed"},
      Code{"PNCL"}
    }
  },
  ResetEvents{
    {
      Name{"doorOpened"}
    }
  },
  Commands{
    {
      Name{"unlockPanel"},
      Code{"PNUL"}
    },
    {
      Name{"lockPanel"},
      Code{"PNLK"}
    },
    {
      Name{"lockDoor"},
      Code{"D1LK"}
    },
    {
      Name{"unlockDoor"},
      Code{"D1UL"}
    }
  },
  State{
    Name{"idle"},
    Actions[
      "unlockDoor",
      "lockPanel"
    ],
    Transitions{
      {
        Event{"doorClosed"},
        ToState{"active"}
      }
    }
  },
  State{
    Name{"active"},
    Transitions{
      {
        Event{"drawOpened"},
        ToState{"waitingForLight"}
      },
      {
        Event{"lightOn"},
        ToState{"waitingForDraw"}
      }
    }
  },
  State{
    Name{"waitingForLight"},
    Transitions{
      {
        Event{"lightOn"},
        ToState{"unlockedPanel"}
      }
    }
  },
  State{
    Name{"waitingForDraw"},
    Transitions{
      {
        Event{"drawOpened"},
        ToState{"unlockedPanel"}
      }
    }
  },
  State{
    Name{"unlockedPanel"},
    Actions[
      "unlockPanel",
      "lockDoor"
    ],
    Transitions{
      {
        Event{"panelClosed"},
        ToState{"idle"}
      }
    }
  }
}

11 thoughts on “Fowler’s DSL example with MGrammar (Draft!)

  1. Hi Lars,

    First of all, thanks for the article.

    I have 3 questions to you:

    1) How would you apply C/C++ style comments in this language? Using interlave?

    2)
    syntax Main =
    NewLines? e:Block(Events, NameAndCode)
    NewLines+ r:Block(ResetEvents, Name)
    NewLines+ c:Block(Commands, NameAndCode)
    NewLines+ s:List(StateBlock)
    NewLines? ….

    doesn’t a token-seperator keyword or something similar exists in MGrammar, instead of using NewLines everywhere? This slightly crowds the grammar imo.

    interleave Ignore = ” “;
    tokenbreak Break = NewLine+;

    would be nice.

    3) I see you use an attribute @{Classification[“Keyword”]} which doesn’t exist in the MGrammar language specification document. Where can I find un-documented attributes?

    Regards.

    Aykut.

    • Hi Aykut,

      1) Since M has C/C++-Style comments, I just stole it from the grammar definition of “M” and modified it slightly

          interleave Ignore = " " | CommentToken;
          token CommentToken  = CommentDelimited | CommentLine;
          token CommentDelimited = "/*" CommentDelimitedContent* "*/";
          token CommentDelimitedContent = ^('*') | '*'  ^('/');
          token CommentLine = "//" CommentLineContent* NewLine;
          token CommentLineContent = ^(NewLineCharacter); // Paragraph Separator
      

      2) I absolutely agree, but MGrammar does not have the concept of separators yet. Forum disskussion on that: http://social.msdn.microsoft.com/Forums/en-US/oslo/thread/3d5cb0ce-ac57-47e0-a17c-55e977f2b56b

      3) Attributes are the extension points to MGrammar. But I don’t know where they are documented. I just saw them in the examples, and I saw that it is possible to write them yourself. You might ask this question in the forum.

  2. Pingback: Why Oslo is Important « Critical Development

  3. Pingback: Simon Zambrovski | Fowler’s DSL example with TLA+

  4. Pingback: Más ejemplos usando Oslo: Galería de ejemplos de MGrammar - Motivos de un ensamblado

  5. Pingback: Más ejemplos usando Oslo: Galería de ejemplos de MGrammar - Miemblogs

  6. Pingback: Bookmarks [2009-11-22] (tamnd) « Scapbi02's Blog

  7. Pingback: Martin Fowlers State Machine in meta# « justinmchase

Leave a comment