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
interleave Ignore = " " | CommentToken; token CommentToken = CommentDelimited | CommentLine; token CommentDelimited = "/*" CommentDelimitedContent* "*/"; token CommentDelimitedContent = ^('*') | '*' ^('/'); token CommentLine = "//" CommentLineContent* NewLine; token CommentLineContent = ^(NewLineCharacter); // Paragraph Separator2) 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.
[...] are lots of bloggers talking about their initial experiences with it, such as Shawn Wildermuth, Lars Corneliussen, and of course Chris Sells and Jeff Pinkston. The most clear and coherent explanation [...]
[...] seems to be popular to write Fowler’s DSL in all languages. I follow this idea and start a series of posts on [...]
[...] State Machine: DSL para la definición de máquinas de estados. [...]
[...] State Machine: DSL para la definición de máquinas de estados. [...]
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:
http://justinmchase.com/2011/10/05/martin-fowlers-state-machine-in-meta/
[...] Fowler’s DSL example with MGrammar (Draft!) « .Net Braindrops [...]
[...] Fowlers State Machine is quickly becoming the 99 bottles of beer on the wall for DSLs. So I implemented it in meta# and will using [...]