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"} } } } }
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
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.
Pingback: Why Oslo is Important « Critical Development
Pingback: Simon Zambrovski | Fowler’s DSL example with TLA+
Pingback: Más ejemplos usando Oslo: Galería de ejemplos de MGrammar - Motivos de un ensamblado
Pingback: Más ejemplos usando Oslo: Galería de ejemplos de MGrammar - Miemblogs
I’m working on a tool that takes MGraph output and generates .net types using templates.
http://www.justnbusiness.com/post/2009/03/11/MetaSharp-code-generation-success!.aspx
Would you mind if I tried to use your grammar here as a sample? It seems like it would translate nicely.
Absolutely! I would love to see that!
2 years later… After a lot of work I finally have a complete sample of this:
Pingback: Bookmarks [2009-11-22] (tamnd) « Scapbi02's Blog
Pingback: Martin Fowlers State Machine in meta# « justinmchase