/*******************************************************************************
* Copyright (c) 2007, 2008 Edgar Espina.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
*******************************************************************************/
package org.deved.antlride.core.formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import org.deved.antlride.core.formatter.AntlrFormatterOptions.BracesStyle;
import org.deved.antlride.core.formatter.AntlrFormatterPreferences.BlankLines;
import org.deved.antlride.core.formatter.AntlrFormatterPreferences.Braces;
import org.deved.antlride.core.formatter.AntlrFormatterPreferences.ControlStatements;
import org.deved.antlride.core.formatter.AntlrFormatterPreferences.Indent;
import org.deved.antlride.core.formatter.AntlrFormatterPreferences.WhiteSpaces;
import org.deved.antlride.core.integration.AntlrLanguageTargetService;
import org.deved.antlride.core.model.GrammarType;
import org.deved.antlride.core.model.IAlternative;
import org.deved.antlride.core.model.IAssign;
import org.deved.antlride.core.model.IBangOperator;
import org.deved.antlride.core.model.IBlock;
import org.deved.antlride.core.model.ICallExpression;
import org.deved.antlride.core.model.ICallExpressionOption;
import org.deved.antlride.core.model.ICompositeStatement;
import org.deved.antlride.core.model.IGrammar;
import org.deved.antlride.core.model.IGrammarAction;
import org.deved.antlride.core.model.IGrammarScope;
import org.deved.antlride.core.model.IImport;
import org.deved.antlride.core.model.IImports;
import org.deved.antlride.core.model.IModelElement;
import org.deved.antlride.core.model.INotOperator;
import org.deved.antlride.core.model.IOption;
import org.deved.antlride.core.model.IOptions;
import org.deved.antlride.core.model.IParameter;
import org.deved.antlride.core.model.IParameters;
import org.deved.antlride.core.model.IRange;
import org.deved.antlride.core.model.IReturns;
import org.deved.antlride.core.model.IRootOperator;
import org.deved.antlride.core.model.IRule;
import org.deved.antlride.core.model.IRuleAction;
import org.deved.antlride.core.model.IRuleCatch;
import org.deved.antlride.core.model.IRuleFinally;
import org.deved.antlride.core.model.IRuleScope;
import org.deved.antlride.core.model.IRuleThrows;
import org.deved.antlride.core.model.IScopeReference;
import org.deved.antlride.core.model.ISemanticPredicate;
import org.deved.antlride.core.model.ISourceElement;
import org.deved.antlride.core.model.IStatement;
import org.deved.antlride.core.model.IStatementAction;
import org.deved.antlride.core.model.ITemplate;
import org.deved.antlride.core.model.ITemplateParameter;
import org.deved.antlride.core.model.IToken;
import org.deved.antlride.core.model.ITokenName;
import org.deved.antlride.core.model.ITokenValue;
import org.deved.antlride.core.model.ITokens;
import org.deved.antlride.core.model.ITreeStatement;
import org.deved.antlride.core.model.IRule.RuleAccessModifier;
import org.deved.antlride.core.model.IStatement.EBNF;
import org.deved.antlride.core.model.ast.AAbstractModelElementVisitor;
import org.deved.antlride.core.model.ast.ModelElementQuery;
import org.deved.antlride.core.util.PerformanceMonitor;
import org.eclipse.dltk.core.ISourceRange;
import org.eclipse.dltk.core.builder.ISourceLineTracker;
import org.eclipse.dltk.utils.TextUtils;
public class AntlrFormatter extends AAbstractModelElementVisitor {
private static class LineTracker {
private ISourceLineTracker lineTracker;
private StringBuilder source;
public LineTracker(StringBuilder buffer) {
lineTracker = TextUtils.createLineTracker(buffer.toString());
source = buffer;
}
public String previousLineAt(int offset) {
int line = lineTracker.getLineNumberOfOffset(offset) - 1;
line = Math.max(0, line);
ISourceRange range = lineTracker.getLineInformation(line);
return line(range);
}
public String lineAt(int offset) {
try {
ISourceRange range = lineTracker
.getLineInformationOfOffset(offset);
return line(range);
} catch (StringIndexOutOfBoundsException ex) {
return EMPTY;
}
}
public String nextLineAt(int offset) {
int line = lineTracker.getLineNumberOfOffset(offset) + 1;
line = Math.min(line, lineTracker.getNumberOfLines());
ISourceRange range = lineTracker.getLineInformation(line);
return line(range);
}
private String line(int start, int end) {
String line = source.substring(start, end);
return line;
}
private String line(ISourceRange range) {
if (range == ISourceLineTracker.NULL_RANGE) {
return EMPTY;
}
int start = range.getOffset();
int end = start + range.getLength();
return line(start, end);
}
}
private static final String BANG = "!";
private static final String ROOT = "^";
private static final String COMMA = ",";
private static final String SEMANTIC_OPERATOR = "=>";
private static final String EMPTY = "";
private static final String AMPERSAND = "@";
private static final String RETURNS = "returns";
private static final String SCOPE = "scope";
private static final String ONE_SPACE = " ";
private static final String SEMI = ";";
private static final String COLON = ":";
private static final String ASSIGN = "=";
private static final String GRAMMAR = "grammar";
private static final String OPTIONS = "options";
private static final String L_CURLY_BRACKET = "{";
private static final String L2_CURLY_BRACKET = "{{";
private static final String R_CURLY_BRACKET = "}";
private static final String R2_CURLY_BRACKET = "}}";
private static final String L_ANGULAR_BRACKET = "<";
private static final String R_ANGULAR_BRACKET = ">";
private static final String L_BRACKET = "[";
private static final String R_BRACKET = "]";
private static final String L_ROUND_BRACKET = "(";
private static final String R_ROUND_BRACKET = ")";
private static final String TOKENS = "tokens";
private static final String RANGE = "..";
private static final String REWRITE_OPERATOR = "->";
private static final String ALT = "|";
private static final String TREE_OPERATOR = "^(";
static boolean DEBUG = false;
private int level = 0;
private StringBuilder target = new StringBuilder();
private AntlrFormatterOptions options;
private String NL;
private int ruleOffset;
private int maxTokenLength;
private int maxOptionLength;
private int startOffset;
private int endOffset;
private AntlrLanguageTargetService languageTarget;
public AntlrFormatter(AntlrFormatterOptions options) {
this.options = options;
NL = options.getString(BlankLines.NEW_LINE);
}
public AntlrLanguageTargetService getLanguageTarget() {
return languageTarget;
}
public AntlrFormatter withLanguageTarget(
AntlrLanguageTargetService languageTarget) {
this.languageTarget = languageTarget;
return this;
}
@Override
public void accept(IModelElement node) {
IGrammar grammar = node.getAdapter(IGrammar.class);
if (grammar.isValid()) {
startOffset = node.sourceStart();
endOffset = node.sourceEnd();
PerformanceMonitor monitor = new PerformanceMonitor().start();
target.setLength(0);
super.accept(node);
monitor.end();
if (DEBUG) {
System.out.printf(" %s- Formatted-Source took %ss, %sms\n",
node.getElementName(), monitor.seconds(), monitor
.milliSecongds());
}
// merge comments
monitor = new PerformanceMonitor().start();
mergeComments(node.getAdapter(IGrammar.class));
monitor.end();
if (DEBUG) {
System.out.printf(" %s- Comments-Added took %ss, %sms\n", node
.getElementName(), monitor.seconds(), monitor
.milliSecongds());
}
} else {
target.append(grammar.getSource());
}
}
private void mergeComments(IGrammar grammar) {
StringBuilder source = new StringBuilder(grammar.getSource());
String[] comments = grammar.getComments();
int commentStart = -1;
int offset = 1;
Pattern pattern = Pattern.compile("\r?\n");
for (String comment : comments) {
// find the comment in the original source
commentStart = source.indexOf(comment, commentStart + offset);
if (commentStart < startOffset || commentStart > endOffset) {
// ignore comment, usually a single rule
continue;
}
offset = comment.length();
// avoid conflict between the end of line
comment = pattern.matcher(comment).replaceAll(NL);
// extract the content that preceded by the comment
String prefix = source.substring(0, commentStart);
// find the "prefix" ignoring whitespace chars
int insertionPoint = findOffset(prefix, startOffset, target);
comment = comment.trim();
// is a single line comment? can be // or /**/
boolean isSingleLineComment = getNumberOfLines(comment) == 0;
LineTracker sourceLineTracker = new LineTracker(source);
String line = sourceLineTracker.lineAt(commentStart);
// remove the comment from the line
line = line.replace(comment, "");
// create the comment
StringBuilder chunk = new StringBuilder();
if (isSingleLineComment) {
// is there anything else in the line?
boolean isInAnEmptyLine = false;
if (line.trim().length() == 0) {
isInAnEmptyLine = true;
} else {
String[] blankPatterns = { ALT, REWRITE_OPERATOR,
TREE_OPERATOR, COLON };
String l = line.trim();
for (int i = 0; i < blankPatterns.length; i++) {
if (blankPatterns[i].equals(l)) {
isInAnEmptyLine = true;
break;
}
}
}
if (isInAnEmptyLine) {
// the next line in the target source
LineTracker targetLineTracker = new LineTracker(target);
String nextLine = targetLineTracker
.nextLineAt(insertionPoint);
// the previous line in the source
String previousLine = sourceLineTracker
.previousLineAt(commentStart);
if (previousLine.length() == 0
|| previousLine.trim().length() == 0) {
// the line before the comment is empty in the source
// add an additional line in the target source
chunk.append(NL);
}
chunk.append(NL);
int firstNoneWS = firstNoneWS(nextLine, 0);
fill(chunk, firstNoneWS);
chunk.append(comment);
} else {
chunk.append(ONE_SPACE).append(comment);
}
} else {
if (commentStart > 0) {
chunk.append(NL);
}
chunk.append(comment).append(NL);
}
// ensure new line
if (isSingleLineComment && !match(target, NL, insertionPoint)) {
chunk.append(NL);
if (insertionPoint > 0) {
int start = target.lastIndexOf(NL, insertionPoint)
+ NL.length();
if (match(target, REWRITE_OPERATOR, start)) {
start += REWRITE_OPERATOR.length();
}
String offsetText = target.substring(start, insertionPoint);
fill(chunk, firstNoneWS(offsetText, 0));
int i = insertionPoint;
while (i < target.length()
&& isWhitespace(target.charAt(i))) {
target.deleteCharAt(i);
}
}
}
target.insert(insertionPoint, chunk);
if (DEBUG) {
System.out.println("inserting:");
System.out.println(chunk);
System.out.println("comment inserted");
System.out.println(target);
}
}
}
private int firstNoneWS(CharSequence charSequence, int start) {
int i = start;
while (i < charSequence.length()
&& isWhitespace(charSequence.charAt(i))) {
i++;
}
return i;
}
private String braceSeperator(BracesStyle bracesStyle) {
switch (bracesStyle) {
case SAME_LINE:
return ONE_SPACE;
case NEXT_LINE:
return NL;
}
throw new UnsupportedOperationException(bracesStyle.name());
}
private void fill(StringBuilder chunk, int length) {
for (int i = 0; i < length; i++) {
chunk.append(ONE_SPACE);
}
}
private boolean match(CharSequence source, String string, int offset) {
int i = 0;
while ((offset >= 0 && (offset + i) < source.length())
&& (i < string.length())
&& source.charAt(offset + i) == string.charAt(i)) {
i++;
}
return i == string.length();
}
private int getNumberOfLines(String comment) {
int start = comment.indexOf(NL);
int lines = 0;
while (start != -1) {
start = comment.indexOf(NL, start + NL.length());
lines++;
}
return lines;
}
// private String nextLineAt(StringBuilder source, int offset) {
// try {
// return lineAt(source, offset
// + (target.indexOf(NL, offset) + NL.length() - offset));
// } catch (StringIndexOutOfBoundsException ex) {
// return EMPTY;
// }
// }
//
// private String lineAt(StringBuilder source, int offset) {
// try {
// ISourceLineTracker lineTracker = TextUtils.createLineTracker(source
// .toString());
// ISourceRange sourceRange = lineTracker
// .getLineInformationOfOffset(offset);
// if (sourceRange == ISourceLineTracker.NULL_RANGE) {
// return EMPTY;
// }
// int start = sourceRange.getOffset();
// int end = start + sourceRange.getLength();
//
// String line = source.substring(start, end);
//
// return line;
// } catch (StringIndexOutOfBoundsException ex) {
// return EMPTY;
// }
// }
private boolean isWhitespace(char ch) {
return Character.isWhitespace(ch);
}
private int findOffset(CharSequence prefix, int offset, CharSequence source) {
int j = 0;
for (int i = offset; i < prefix.length();) {
char ch1 = prefix.charAt(i);
if (isWhitespace(ch1)) {
i++;
continue;
}
char ch2 = source.charAt(j);
if (isWhitespace(ch2)) {
j++;
continue;
}
if (ch1 != ch2) {
break;
}
j++;
i++;
}
return j;
}
public String content() {
return target.toString();
}
@Override
public boolean visitGrammar(IGrammar node) {
boolean hasDoc = node.getPlainDocumentation() != null;
if (hasDoc) {
target.append(node.getPlainDocumentation());
if (options.getInt(BlankLines.LINES_BEFORE_GRAMMAR_DECLARATION) == 0) {
// ensure one line between the doc and the grammar decl
target.append(NL);
}
}
target.append(blankLines(options
.getInt(BlankLines.LINES_BEFORE_GRAMMAR_DECLARATION)));
if (node.getGrammarType() != GrammarType.COMBINED) {
target.append(node.getGrammarType().description())
.append(ONE_SPACE);
}
target.append(GRAMMAR).append(ONE_SPACE).append(node.getElementName())
.append(SEMI);
target.append(blankLines(options
.getInt(BlankLines.LINES_AFTER_GRAMMAR_DECLARATION)));
return super.visitGrammar(node);
}
private String blankLines(int count) {
StringBuilder buff = new StringBuilder();
for (int i = 0; i < count; i++) {
buff.append(NL);
}
return buff.toString();
}
@Override
public boolean visitImports(IImports node) {
target.append("import").append(ONE_SPACE);
return super.visitImports(node);
}
@Override
public boolean visitImport(IImport node) {
target.append(node.getElementName()).append(COMMA).append(ONE_SPACE);
return super.visitImport(node);
}
@Override
public void endvisitImports(IImports node) {
// eat the last ', '
target.setLength(target.length() - 2);
target.append(SEMI).append(NL).append(NL);
super.endvisitImports(node);
}
@Override
public boolean visitGrammarOptions(IOptions node) {
target
.append(blankLines(options
.getInt(BlankLines.LINES_BEFORE_TOKENS)));
target.append(OPTIONS);
// braces option
target.append(braceSeperator(options.getEnum(Braces.OPTIONS,
BracesStyle.class)));
target.append(L_CURLY_BRACKET).append(NL);
if (options.getBoolean(Indent.ALIGN_OPTIONS_IN_COLUMNS)) {
// find the largest token
IOption[] options = node.getOptions();
maxOptionLength = 0;
for (IOption token : options) {
maxOptionLength = Math.max(token.getElementName().length(),
maxOptionLength);
}
}
if (options.getBoolean(Indent.OPTIONS))
level(1);
return super.visitGrammarOptions(node);
}
@Override
public boolean visitGrammarOption(IOption node) {
target.append(getIndent()).append(node.getName().getElementName());
if (options.getBoolean(Indent.ALIGN_OPTIONS_IN_COLUMNS)) {
int size = maxOptionLength - node.getElementName().length();
if (options.getBoolean(WhiteSpaces.BEFORE_AFTER_OPTION)) {
size += 1;
}
target.append(getIndent(1, size));
} else {
if (options.getBoolean(WhiteSpaces.BEFORE_AFTER_OPTION)) {
target.append(ONE_SPACE);
}
}
target.append(ASSIGN);
if (options.getBoolean(WhiteSpaces.BEFORE_AFTER_OPTION)) {
target.append(ONE_SPACE);
}
target.append(node.getValue().getElementName()).append(SEMI).append(NL);
return super.visitGrammarOption(node);
}
@Override
public void endvisitGrammarOptions(IOptions node) {
target.append(R_CURLY_BRACKET);
target
.append(blankLines(options
.getInt(BlankLines.LINES_AFTER_OPTIONS)));
if (options.getBoolean(Indent.OPTIONS))
level(-1);
super.endvisitGrammarOptions(node);
}
@Override
public boolean visitTokens(ITokens node) {
target
.append(blankLines(options
.getInt(BlankLines.LINES_BEFORE_TOKENS)));
target.append(TOKENS);
target.append(braceSeperator(options.getEnum(Braces.TOKENS,
BracesStyle.class)));
target.append(L_CURLY_BRACKET).append(NL);
if (options.getBoolean(Indent.ALIGN_TOKENS_IN_COLUMNS)) {
// find the largest token
IToken[] tokens = node.getTokens();
maxTokenLength = 0;
for (IToken token : tokens) {
if (token.hasValue()) {
maxTokenLength = Math.max(token.getElementName().length(),
maxTokenLength);
}
}
}
if (options.getBoolean(Indent.TOKENS))
level(1);
return super.visitTokens(node);
}
@Override
public boolean visitTokenName(ITokenName node) {
target.append(getIndent()).append(node.getElementName());
return super.visitTokenName(node);
}
@Override
public boolean visitTokenValue(ITokenValue node) {
if (options.getBoolean(Indent.ALIGN_TOKENS_IN_COLUMNS)) {
IToken token = node.getParent().getAdapter(IToken.class);
ITokenName tokenName = token.getName();
int size = (maxTokenLength - tokenName.getElementName().length());
if (options.getBoolean(WhiteSpaces.BEFORE_AFTER_TOKEN)) {
size += 1;
}
target.append(getIndent(1, size));
} else {
if (options.getBoolean(WhiteSpaces.BEFORE_AFTER_TOKEN)) {
target.append(ONE_SPACE);
}
}
target.append(ASSIGN);
if (options.getBoolean(WhiteSpaces.BEFORE_AFTER_TOKEN)) {
target.append(ONE_SPACE);
}
target.append(node.getElementName());
return super.visitTokenValue(node);
}
@Override
public void endvisitToken(IToken node) {
target.append(SEMI).append(NL);
super.endvisitToken(node);
}
@Override
public void endvisitTokens(ITokens node) {
target.append(R_CURLY_BRACKET);
target
.append(blankLines(options
.getInt(BlankLines.LINES_AFTER_TOKENS)));
if (options.getBoolean(Indent.TOKENS))
level(-1);
super.endvisitTokens(node);
}
@Override
public boolean visitGrammarAction(IGrammarAction node) {
target
.append(blankLines(options
.getInt(BlankLines.LINES_BEFORE_ACTION)));
target.append(AMPERSAND).append(node.getElementName());
target.append(braceSeperator(options.getEnum(Braces.ACTIONS,
BracesStyle.class)));
target.append(L_CURLY_BRACKET).append(NL);
target.append(processActionCode(node.getAction()));
target.append(NL).append(R_CURLY_BRACKET);
target
.append(blankLines(options
.getInt(BlankLines.LINES_AFTER_ACTION)));
return super.visitGrammarAction(node);
}
private String processActionCode(ISourceElement source) {
return source == null ? EMPTY : processActionCode(source.getText());
}
private String processActionCode(String source) {
StringBuilder buff = new StringBuilder();
String[] starts = { L2_CURLY_BRACKET, L_CURLY_BRACKET, L_CURLY_BRACKET,
L_BRACKET };
String[] ends = { R2_CURLY_BRACKET, R_CURLY_BRACKET,
R_CURLY_BRACKET + "?", R_BRACKET };
boolean matches = false;
for (int i = 0; i < starts.length; i++) {
if (source.startsWith(starts[i]) && source.endsWith(ends[i])) {
buff.append(source, starts[i].length(), source.length()
- ends[i].length());
matches = true;
break;
}
}
if (!matches) {
// no match
buff.append(source.trim());
}
String action = buff.toString().trim();
if (action.length() > 0 && languageTarget != null) {
action = languageTarget.format(action);
}
return action;
}
@Override
public boolean visitGrammarScope(IGrammarScope node) {
target
.append(blankLines(options
.getInt(BlankLines.LINES_BEFORE_SCOPE)));
target.append(SCOPE).append(ONE_SPACE).append(node.getElementName());
target.append(braceSeperator(options.getEnum(Braces.SCOPES,
BracesStyle.class)));
target.append(L_CURLY_BRACKET).append(NL);
target.append(processActionCode(node.getText()));
return true;
}
@Override
public void endvisitGrammarScope(IGrammarScope node) {
target.append(NL).append(R_CURLY_BRACKET);
target.append(blankLines(options.getInt(BlankLines.LINES_AFTER_SCOPE)));
}
@Override
public boolean visitBlockOptions(IOptions node) {
if (endsWith(NL)) {
target.setLength(target.length() - NL.length());
target.append(ONE_SPACE);
}
target.append(OPTIONS).append(ONE_SPACE).append(L_CURLY_BRACKET);
return true;
}
@Override
public boolean visitBlockOption(IOption node) {
if (options.getBoolean(Indent.BLOCK_OPTIONS)) {
target.append(getIndent(1));
}
target.append(node.getName().getElementName()).append(ASSIGN).append(
node.getValue().getElementName()).append(SEMI)
.append(ONE_SPACE);
return true;
}
@Override
public void endvisitBlockOptions(IOptions node) {
// eat the last space
target.setLength(target.length() - 1);
target.append(R_CURLY_BRACKET).append(COLON);
}
@Override
public boolean visitRuleScope(IRuleScope node) {
target.append(blankLines(options
.getInt(BlankLines.LINES_BEFORE_RULE_SCOPE)));
target.append(SCOPE);
target.append(braceSeperator(options.getEnum(Braces.RULE_SCOPES,
BracesStyle.class)));
target.append(L_CURLY_BRACKET).append(NL);
target.append(processActionCode(node.getText()));
return true;
}
@Override
public void endvisitRuleScope(IRuleScope node) {
target.append(NL).append(R_CURLY_BRACKET);
target.append(blankLines(options
.getInt(BlankLines.LINES_AFTER_RULE_SCOPE)));
}
@Override
public boolean visitRuleScopeReference(IScopeReference node) {
IRule rule = (IRule) node.getParent();
int index = 0;
IScopeReference[] scopesReferences = rule.getScopesReferences();
for (int i = 0; i < scopesReferences.length; i++) {
if (scopesReferences[i] == node) {
index = i;
break;
}
}
if (index == 0) {
target.append(blankLines(options
.getInt(BlankLines.LINES_BEFORE_RULE_SCOPE)));
target.append(SCOPE).append(ONE_SPACE);
}
target.append(node.getElementName());
if (index < scopesReferences.length - 1) {
target.append(COMMA).append(ONE_SPACE);
} else {
target.append(SEMI);
target.append(blankLines(options
.getInt(BlankLines.LINES_AFTER_RULE_SCOPE)));
}
return true;
}
@Override
public boolean visitRuleOptions(IOptions node) {
target.append(blankLines(options
.getInt(BlankLines.LINES_BEFORE_RULE_OPTIONS)));
target.append(OPTIONS);
target.append(braceSeperator(options.getEnum(Braces.RULE_OPTIONS,
BracesStyle.class)));
target.append(L_CURLY_BRACKET).append(NL);
return true;
}
@Override
public boolean visitRuleOption(IOption node) {
if (options.getBoolean(Indent.RULE_OPTIONS)) {
target.append(getIndent(1));
}
target.append(node.getElementName());
if (options.getBoolean(WhiteSpaces.BEFORE_AFTER_OPTION)) {
target.append(ONE_SPACE);
}
target.append(ASSIGN);
if (options.getBoolean(WhiteSpaces.BEFORE_AFTER_OPTION)) {
target.append(ONE_SPACE);
}
target.append(node.getValue().getElementName()).append(SEMI).append(NL);
return true;
}
@Override
public void endvisitRuleOptions(IOptions node) {
target.append(R_CURLY_BRACKET);
target.append(blankLines(options
.getInt(BlankLines.LINES_AFTER_RULE_OPTIONS)));
}
@Override
public boolean visitRuleAction(IRuleAction node) {
target.append(blankLines(options
.getInt(BlankLines.LINES_BEFORE_RULE_ACTION)));
target.append(AMPERSAND).append(node.getElementName());
target.append(braceSeperator(options.getEnum(Braces.RULE_ACTIONS,
BracesStyle.class)));
target.append(L_CURLY_BRACKET).append(NL);
target.append(processActionCode(node.getAction().getText()));
return true;
}
@Override
public void endvisitRuleAction(IRuleAction node) {
target.append(NL).append(R_CURLY_BRACKET);
target.append(blankLines(options
.getInt(BlankLines.LINES_AFTER_RULE_ACTION)));
}
@Override
public boolean visitRuleReturns(IReturns node) {
if (options.getBoolean(ControlStatements.NL_BEFORE_RULE_RETURNS)) {
target.append(NL).append(getIndent(1));
} else {
target.append(ONE_SPACE);
}
target.append(RETURNS).append(ONE_SPACE).append(L_BRACKET).append(
processActionCode(node.getText())).append(R_BRACKET);
return true;
}
@Override
public boolean visitRule(IRule node) {
IGrammar grammar = node.getAdapter(IGrammar.class);
if (grammar.firstRule() == node) {
target.append(blankLines(options
.getInt(BlankLines.LINES_BEFORE_FIRST_RULE)));
} else {
target.append(blankLines(options
.getInt(BlankLines.LINES_BEFORE_RULE)));
}
if (node.getPlainDocumentation() != null) {
target.append(node.getPlainDocumentation()).append(NL);
}
RuleAccessModifier accessModifier = node.getAccessModifier();
if (accessModifier != RuleAccessModifier.PUBLIC) {
target.append(accessModifier.description());
if (options.getBoolean(ControlStatements.NL_AFTER_RULE_MODIFIER)) {
target.append(NL);
} else {
target.append(ONE_SPACE);
}
}
target.append(node.getElementName());
target.append(node.getAstSuffix().description());
ruleOffset = target.length();
if (options.getBoolean(Indent.RULE)) {
level(1);
}
return true;
}
@Override
public boolean visitRuleThrows(IRuleThrows node) {
if (options.getBoolean(ControlStatements.NL_BEFORE_RULE_THROWS)) {
target.append(NL).append(getIndent(1));
} else {
target.append(ONE_SPACE);
}
target.append(node.getElementName()).append(ONE_SPACE);
for (String exception : node.getExceptions()) {
target.append(exception).append(COMMA).append(ONE_SPACE);
}
// eat the last ", "
target.setLength(target.length() - COMMA.length() - ONE_SPACE.length());
return true;
}
@Override
public boolean visitRuleCatch(IRuleCatch node) {
target.append(node.getElementName()).append(ONE_SPACE).append(
node.getException()).append(ONE_SPACE).append(L_CURLY_BRACKET)
.append(NL);
target.append(processActionCode(node.getAction().getText()));
target.append(NL).append(R_CURLY_BRACKET).append(NL);
return true;
}
@Override
public boolean visitRuleFinally(IRuleFinally node) {
target.append(node.getElementName()).append(ONE_SPACE).append(
L_CURLY_BRACKET).append(NL);
target.append(processActionCode(node.getAction().getText()));
target.append(NL).append(R_CURLY_BRACKET).append(NL);
return true;
}
protected boolean endsWith(String suffix) {
return endsWith(target, suffix);
}
protected boolean endsWith(StringBuilder builder, String suffix) {
int k = 0;
for (int i = builder.length() - suffix.length(); i < builder.length(); i++) {
char ch = builder.charAt(i);
if (suffix.charAt(k++) != ch) {
return false;
}
}
return true;
}
protected void ensureIndentAtBegining() {
if (!isWhitespace(target.charAt(target.length() - 1))) {
target.append(ONE_SPACE);
} else {
target.append(getIndent());
}
}
@Override
public void endvisitRule(IRule node) {
boolean completelyEmpty = node.isEmpty()
&& !(node.hasActions() || node.hasCatchs() || node.hasOptions()
|| node.hasParameters() || node.hasReturns()
|| node.hasScopes() || node.hasScopesReferences());
if (completelyEmpty
&& options.getBoolean(ControlStatements.EMPTY_RULE_ON_ONE_LINE)) {
// completely empty!
target.setLength(target.length() - (target.length() - ruleOffset));
target.append(COLON).append(ONE_SPACE).append(SEMI);
} else {
if (options.getBoolean(ControlStatements.NL_BEFORE_RULE_END)) {
if (!endsWith(NL)) {
target.append(NL);
}
target.append(getIndent());
} else {
if (endsWith(NL)) {
target.setLength(target.length() - NL.length());
}
}
target.append(SEMI);
}
target.append(blankLines(options.getInt(BlankLines.LINES_AFTER_RULE)));
if (options.getBoolean(Indent.RULE)) {
level(-1);
}
}
@Override
public boolean visitRuleBody(IBlock node) {
if (options.getBoolean(ControlStatements.NL_BEFORE_RULE_COLON)) {
if (!endsWith(NL)) {
target.append(NL);
}
target.append(getIndent());
} else {
if (endsWith(NL)) {
target.setLength(target.length() - NL.length());
}
if (!endsWith(ONE_SPACE)) {
target.append(ONE_SPACE);
}
}
target.append(COLON);
target.append(NL);
return visitBlock(node);
}
@Override
public void endvisitRuleBody(IBlock node) {
endvisitBlock(node);
}
@Override
public boolean visitSemanticPredicate(ISemanticPredicate node) {
ensureIndentAtBegining();
target.append(L_CURLY_BRACKET).append(
processActionCode(node.getCondition().getText())).append(
R_CURLY_BRACKET).append("?");
target.append(node.getPredicateType().description());
return true;
}
@Override
public void endvisitBangOperator(IBangOperator node) {
if (endsWith(NL)) {
target.insert(target.length() - NL.length(), BANG);
} else {
target.append(BANG);
}
target.append(node.getEbnfOperator().description());
}
@Override
public void endvisitRootOperator(IRootOperator node) {
if (endsWith(NL)) {
target.insert(target.length() - NL.length(), ROOT);
} else {
target.append(ROOT);
}
target.append(node.getEbnfOperator().description());
}
@Override
public void endvisitSyntacticPredicateCondition(IStatement node) {
level(1);
ensureIndentAtBegining();
target.append(SEMANTIC_OPERATOR);
level(-1);
}
@Override
public boolean visitAssign(IAssign node) {
if (endsWith(L_ROUND_BRACKET)) {
if (options.getBoolean(WhiteSpaces.BEFORE_AFTER_BLOCK_PARENTHESIS)) {
ensureIndentAtBegining();
}
} else {
ensureIndentAtBegining();
}
target.append(node.getVariable().getElementName());
if (options.getBoolean(WhiteSpaces.BEFORE_AFTER_ASSIGN)) {
target.append(ONE_SPACE);
}
target.append(node.getOperator().getText().trim());
return super.visitAssign(node);
}
@Override
public void endvisitAssign(IAssign node) {
target.append(node.getEbnfOperator().description());
super.endvisitAssign(node);
}
@Override
public boolean visitStatementAction(IStatementAction node) {
ensureIndentAtBegining();
int newLineStart = target.lastIndexOf(NL);
String lastLine = target.substring(newLineStart);
int whiteSize = firstNoneWS(lastLine, 0) + lastLine.trim().length() - 1;
target.append(NL);
fill(target, whiteSize);
String lbracket = L_CURLY_BRACKET;
String rbracket = R_CURLY_BRACKET;
if (node.isForced()) {
lbracket = L2_CURLY_BRACKET;
rbracket = R2_CURLY_BRACKET;
}
target.append(lbracket).append(NL);
String[] actionCode = processActionCode(node.getAction().getText())
.split(NL);
for (String lineOfActionCode : actionCode) {
fill(target, whiteSize + lbracket.length());
target.append(lineOfActionCode).append(NL);
}
fill(target, whiteSize);
target.append(rbracket);
target.append(NL);
return super.visitStatementAction(node);
}
@Override
public void endvisitStatementAction(IStatementAction node) {
super.endvisitStatementAction(node);
}
@Override
public boolean visitTreeStatement(ITreeStatement node) {
boolean firstBlockAfterRewriteOp = endsWith(REWRITE_OPERATOR);
if (firstBlockAfterRewriteOp) {
if (options.getBoolean(ControlStatements.NL_AFTER_REWRITE_OPERATOR)) {
if (!endsWith(NL)) {
target.append(NL);
}
target.append(getIndent());
} else {
target.append(ONE_SPACE);
}
} else {
if (!endsWith(NL)) {
target.append(NL);
}
target.append(getIndent());
}
target.append(node.getElementName());
if (options.getBoolean(WhiteSpaces.BEFORE_AFTER_BLOCK_PARENTHESIS)) {
target.append(ONE_SPACE);
}
boolean isMultipleLineBlock = isMultipleLineBlock(node);
if (isMultipleLineBlock) {
target.append(NL);
}
level(1);
return super.visitTreeStatement(node);
}
@Override
public void endvisitTreeStatement(ITreeStatement node) {
boolean isMultipleLineBlock = isMultipleLineBlock(node);
level(-1);
if (isMultipleLineBlock) {
if (!endsWith(NL)) {
target.append(NL);
}
target.append(getIndent());
}
if (options.getBoolean(WhiteSpaces.BEFORE_AFTER_BLOCK_PARENTHESIS)
|| isMultipleLineBlock) {
target.append(ONE_SPACE);
}
if (!options.getBoolean(ControlStatements.NL_AFTER_REWRITE_OPERATOR)) {
target.append(ONE_SPACE);
}
target.append(R_ROUND_BRACKET).append(
node.getEbnfOperator().description());
target.append(NL);
super.endvisitTreeStatement(node);
}
@Override
public boolean visitBlock(IBlock node) {
if (node.getLeftParenthesis() != null) {
boolean isMultipleLineBlock = isMultipleLineBlock(node);
if (isMultipleLineBlock) {
if (!endsWith(NL)) {
target.append(NL);
}
target.append(getIndent());
} else {
ensureIndentAtBegining();
}
if (node.getParent() instanceof INotOperator) {
target.append(node.getParent().getElementName());
}
target.append(L_ROUND_BRACKET);
if (isMultipleLineBlock) {
target.append(NL);
}
if (options.getBoolean(Indent.BLOCKS)) {
level(1);
}
}
return true;
}
private boolean isMultipleLineBlock(ICompositeStatement node) {
IGrammar grammar = (IGrammar) node.getEnclosingRule().getParent();
if (grammar.isTreeParserGrammar())
return true;
IModelElement[] elements = ModelElementQuery.collectBlocksOrAlts(node);
Map<IModelElement, Integer> map = new HashMap<IModelElement, Integer>();
for (int i = 0; i < elements.length; i++) {
ICompositeStatement element = (ICompositeStatement) elements[i];
IModelElement parent = element.getParent();
int count = map.get(parent) == null ? 0 : map.get(parent);
count++;
if (count > 1) {
// found!
return true;
}
map.put(parent, count);
}
return false;
}
private boolean isMultipleLineBlock(ITreeStatement node) {
IModelElement[] elements = ModelElementQuery
.collectTreeStatements(node);
return elements.length > 0;
}
@Override
public void endvisitBlock(IBlock node) {
boolean multipleLineBlock = isMultipleLineBlock(node);
if (node.getRightParenthesis() != null) {
if (!endsWith(NL)) {
if (multipleLineBlock) {
// alternative
target.append(NL);
}
}
if (options.getBoolean(Indent.BLOCKS)) {
if (multipleLineBlock) {
target.append(getIndent(level(0) - 1));
} else {
if (endsWith(NL)) {
target.setLength(target.length() - NL.length());
}
ensureIndentAtBegining();
}
level(-1);
} else {
if (multipleLineBlock) {
target.append(getIndent(level(0)));
}
}
if (node.getParent() instanceof INotOperator) {
target.append(ONE_SPACE);
}
if (!multipleLineBlock
&& (!options
.getBoolean(WhiteSpaces.BEFORE_AFTER_BLOCK_PARENTHESIS) && endsWith(ONE_SPACE))) {
target.setLength(target.length() - ONE_SPACE.length());
}
target.append(R_ROUND_BRACKET);
}
if (multipleLineBlock) {
if (endsWith(NL)) {
// ebnf
target.insert(target.length() - NL.length(), node
.getEbnfOperator().description());
} else {
target.append(node.getEbnfOperator().description()).append(NL);
}
} else {
if (endsWith(NL)) {
target.setLength(target.length() - NL.length());
}
target.append(node.getEbnfOperator().description());
}
super.endvisitBlock(node);
}
@Override
public boolean visitRewriteBlock(IBlock node) {
if (!endsWith(NL)) {
target.append(NL);
}
if (options.getBoolean(Indent.REWRITE_OPERATOR)) {
level(1);
target.append(getIndent());
}
target.append(REWRITE_OPERATOR);
level(1);
return true;
}
@Override
public void endvisitRewriteBlock(IBlock node) {
if (options.getBoolean(Indent.REWRITE_OPERATOR)) {
level(-1);
}
level(-1);
}
@Override
public void endvisitAlternative(IAlternative node) {
ICompositeStatement cmpstt = (ICompositeStatement) node.getParent();
if (node.getNumber() < cmpstt.size() - 1) {
if (!endsWith(NL)) {
target.append(NL);
}
if (node.getOperator().equals(REWRITE_OPERATOR)) {
// rewrite alternative
// reduce the indent level
level(-1);
target.append(getIndent());
level(1);
} else {
target.append(getIndent());
}
target.append(node.getOperator());
}
}
@Override
public void endvisitNotOperator(INotOperator node) {
if (node.getEbnfOperator() != EBNF.NONE) {
if (endsWith(NL)) {
target.insert(target.length() - NL.length(), node
.getEbnfOperator().description());
} else {
target.append(node.getEbnfOperator().description());
}
}
}
@Override
public boolean visitCallExpression(ICallExpression node) {
int k = target.lastIndexOf(NL);
if (k > 0 && k == target.length() - NL.length()) {
target.append(getIndent());
}
if (!isWhitespace(target.charAt(target.length() - 1))) {
if (!endsWith(RANGE)) {
if (endsWith(ASSIGN)) {
if (options.getBoolean(WhiteSpaces.BEFORE_AFTER_ASSIGN)) {
target.append(ONE_SPACE);
}
} else if (endsWith(L_ROUND_BRACKET)) {
if (options
.getBoolean(WhiteSpaces.BEFORE_AFTER_BLOCK_PARENTHESIS)) {
target.append(ONE_SPACE);
}
} else {
target.append(ONE_SPACE);
}
}
}
if (node.getParent() instanceof INotOperator) {
target.append(node.getParent().getElementName());
}
if (node.hasLabel()) {
target.append("$");
}
target.append(node.getElementName());
// call expression options
ICallExpressionOption[] options = node.getOptions();
StringBuilder optBuff = new StringBuilder();
for (ICallExpressionOption option : options) {
optBuff.append(option.getOptionName().getText());
if (option.hasOptionValue()) {
optBuff.append(ASSIGN);
optBuff.append(option.getOptionValue().getText());
}
optBuff.append(COMMA).append(ONE_SPACE);
}
if (optBuff.length() > 0) {
target.append(L_ANGULAR_BRACKET);
optBuff.setLength(optBuff.length()
- (COMMA.length() + ONE_SPACE.length()));
target.append(optBuff);
target.append(R_ANGULAR_BRACKET);
}
// EBNF operator
if (node.getEbnfOperator() != EBNF.NONE && !node.hasParameters()) {
target.append(node.getEbnfOperator().description());
}
IRange range = node.getParent().getAdapter(IRange.class);
if (range != null) {
if (range.getRight() == node) {
target.append(range.getEbnfOperator().description());
} else {
target.append(range.getElementName());
}
}
return true;
}
@Override
public boolean visitRuleParameters(IParameters node) {
if (options.getBoolean(ControlStatements.NL_BEFORE_RULE_ARGS)) {
target.append(NL).append(getIndent(1));
}
target.append(L_BRACKET);
target.append(node.getAction().getText());
return true;
}
@Override
public void endvisitRuleParameters(IParameters node) {
target.append(R_BRACKET);
}
public boolean visitCallParameters(IParameters node) {
visitRuleParameters(node);
return true;
}
@Override
public void endvisitCallParameters(IParameters node) {
endvisitRuleParameters(node);
ICallExpression callExpression = node.getParent().getAdapter(
ICallExpression.class);
target.append(callExpression.getEbnfOperator().description());
}
@Override
public boolean visitCallParameter(IParameter node) {
visitRuleParameter(node);
return true;
}
@Override
public void endvisitCallParameter(IParameter node) {
endvisitRuleParameter(node);
}
@Override
public boolean visitTemplate(ITemplate node) {
if (options.getBoolean(ControlStatements.NL_AFTER_REWRITE_OPERATOR)) {
target.append(NL);
target.append(getIndent());
}
if (node.isNamed()) {
target.append(node.getName().getText());
} else {
if (node.isSimpleActionTemplate()) {
target.append(node.getAction().getText());
} else {
target.append(L_ROUND_BRACKET);
target.append(node.getAction().getText());
target.append(R_ROUND_BRACKET);
}
}
if (!node.isSimpleActionTemplate()) {
target.append(L_ROUND_BRACKET);
}
return true;
}
@Override
public boolean visitTemplateParameter(ITemplateParameter node) {
target.append(node.getElementName());
if (options.getBoolean(WhiteSpaces.BEFORE_AFTER_ASSIGN)) {
target.append(ONE_SPACE);
}
target.append(ASSIGN);
if (options.getBoolean(WhiteSpaces.BEFORE_AFTER_ASSIGN)) {
target.append(ONE_SPACE);
}
target.append(node.getAction().getText());
target.append(COMMA).append(ONE_SPACE);
return true;
}
@Override
public void endvisitTemplate(ITemplate node) {
if (node.hasParameters()) {
// eat ", "
target.setLength(target.length() - 2);
}
if (!node.isSimpleActionTemplate()) {
target.append(R_ROUND_BRACKET);
}
if (node.isInline()) {
target.append(node.getInlineTemplate().getText());
}
target.append(NL);
}
private String getIndent() {
return getIndent(this.level);
}
private String getIndent(int level) {
if (level == 0)
return EMPTY;
return getIndent(level, options.getInt(Indent.INDENTATION_SIZE));
}
private String getIndent(int level, int size) {
if (level == 0)
return EMPTY;
String spaceChar = options.getString(Indent.TAB_CHAR);
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < (size * level); i++) {
buffer.append(spaceChar);
}
return buffer.toString();
}
private int level(int level) {
this.level += level;
return this.level;
}
@Override
public String toString() {
return target.toString();
}
}