Monday, February 7, 2011

Quickly formatting DSLs with Xtext

One typical task of developing new domain specific languages with Xtext is to customize the formatting in order to have a nice text layout. If the majority of grammar keywords should be formatted in the same way and only a few in a specific way, a generic formatter could be used. As an example, for DSLs with many key-value pairs (e.g. domain models), one might want to indent everything between curly braces and start a new line before each keyword. Instead of manually listing the keywords for the findKeywords()-method of the IGrammarAccess, GrammarUtil.getAllKeywords() can be used to get all the keywords, so a generic formatting method could look like this:
public class GenericFormatter {

/**
* In your implementation of
* {@link org.eclipse.xtext.formatting.impl.AbstractDeclarativeFormatter#configureFormatting(org.eclipse.xtext.formatting.impl.FormattingConfig)}
* you may call this generic formatting method first. It indents blocks between curly braces and sets a linewrap
* before each keyword. Add your own behavior afterwards, e.g.
*
* keywords = grammar.findKeywords(...);
* for (final Keyword keyword : keywords) {
* config.setNoLinewrap().before(keyword);
* }
*/
public static void genericFormatting(final FormattingConfig config, final IGrammarAccess grammar) {
for (final Pair pair : grammar.findKeywordPairs("{", "}")) { //$NON-NLS-1$ //$NON-NLS-2$
// a space before the first '{'
config.setSpace(" ").before(pair.getFirst()); //$NON-NLS-1$
// indentation between
config.setIndentation(pair.getFirst(), pair.getSecond());
// and a linewrap before the last '{'
config.setLinewrap(1).before(pair.getSecond());
}

// linewrap before all keywords
final Set allKeywords = GrammarUtil.getAllKeywords(grammar.getGrammar());
final List keywords = grammar.findKeywords(allKeywords.toArray(new String[allKeywords.size()]));
for (final Keyword keyword : keywords) {
config.setLinewrap().before(keyword);
}

}
}
It could be called inside the configureFormatting()-method in [NameOfTheDSL]Formatter. Formatting code that overrides this behavior for specific keywords can be added after the call to the generic method like this:
keywords = grammar.findKeywords([keywords w/o wrap before]);
for (final Keyword keyword : keywords) {
config.setNoLinewrap().before(keyword);
}