package de.ugoe.cs.swe.bnftools.ui.formatter; import java.util.ArrayList; import java.util.Stack; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.parsetree.AbstractNode; import org.eclipse.xtext.parsetree.CompositeNode; import org.eclipse.xtext.parsetree.LeafNode; import org.eclipse.xtext.parsetree.NodeUtil; import de.ugoe.cs.swe.bnftools.ebnf.Atom; import de.ugoe.cs.swe.bnftools.ebnf.BnfEntry; import de.ugoe.cs.swe.bnftools.ebnf.DefinitionList; import de.ugoe.cs.swe.bnftools.ebnf.DeltaEntry; import de.ugoe.cs.swe.bnftools.ebnf.EtsiBnf; import de.ugoe.cs.swe.bnftools.ebnf.ExtRule; import de.ugoe.cs.swe.bnftools.ebnf.GlobalCombinator; import de.ugoe.cs.swe.bnftools.ebnf.GroupedSequence; import de.ugoe.cs.swe.bnftools.ebnf.HookCombinator; import de.ugoe.cs.swe.bnftools.ebnf.Import; import de.ugoe.cs.swe.bnftools.ebnf.ImportSection; import de.ugoe.cs.swe.bnftools.ebnf.MergeEntry; import de.ugoe.cs.swe.bnftools.ebnf.MergeRule; import de.ugoe.cs.swe.bnftools.ebnf.OptionalSequence; import de.ugoe.cs.swe.bnftools.ebnf.RepeatedSequence; import de.ugoe.cs.swe.bnftools.ebnf.Rule; import de.ugoe.cs.swe.bnftools.ebnf.RuleCombinator; import de.ugoe.cs.swe.bnftools.ebnf.RuleReference; import de.ugoe.cs.swe.bnftools.ebnf.SectionHeading; import de.ugoe.cs.swe.bnftools.ebnf.SingleDefinition; import de.ugoe.cs.swe.bnftools.ebnf.StringRule; import de.ugoe.cs.swe.bnftools.ebnf.Term; import de.ugoe.cs.swe.bnftools.visitor.EbnfVisitor; public class EbnfFormatterVisitor extends EbnfVisitor { protected StringBuffer buf; protected FormatterConfig config; protected int bufferPositionFormattedTextNoWhitespaces = 0; protected int bufferPositionOriginalText = 0; protected int allCommentsPosition = 0; protected ArrayList allComments = new ArrayList(); protected boolean lastWasSectionHeading=false; protected CompositeNode parserEtsiBnfNode; protected String originalText; protected int bufferPositionFormattedText; protected String formattedText; protected String formattedTextNoWhitespaces; protected String originalTextNoWhitespaces; protected int newLineOffsetCounter = 0; protected int rightHandSideRuleOffset = 0; protected Stack ruleSpacingStack = new Stack(); protected Double averageSingleDefinitionLength; public EbnfFormatterVisitor(EObject rootNode, FormatterConfig config) { super(rootNode); this.config = config; buf = new StringBuffer(); } public EbnfFormatterVisitor(FormatterConfig config) { this.config = config; buf = new StringBuffer(); } public StringBuffer getBuf() { return buf; } protected boolean isCommentNode(LeafNode node) { if ((node.getText().trim().startsWith("//") || node.getText().trim().startsWith("/*")) && (node.isHidden())) return true; return false; } protected void collectAllComments(CompositeNode node) { for (int i=0; i < node.getChildren().size(); i++) { AbstractNode currentNode = node.getChildren().get(i); if (currentNode instanceof LeafNode) { LeafNode leafNode = (LeafNode) currentNode; if (isCommentNode(leafNode)) { allComments.add(leafNode); } } if (currentNode instanceof CompositeNode) { collectAllComments((CompositeNode) currentNode); } } } protected boolean isSingleLineComment(String str) { if (str.startsWith("//")) return true; return false; } protected boolean isMultiLineComment(String str) { if (str.startsWith("/*")) return true; return false; } protected boolean isWhitespace(char ch) { if ((ch==' ') || (ch == '\t') || (ch == '\n') || (ch == '\r')) return true; return false; } protected void skipWhitespacesOriginalText() { while (bufferPositionOriginalText < originalText.length() && isWhitespace(originalText.charAt(bufferPositionOriginalText))) { bufferPositionOriginalText++; } } protected void skipWhitespacesFormattedText(StringBuffer result) { while (bufferPositionFormattedText < formattedText.length() && isWhitespace(formattedText.charAt(bufferPositionFormattedText))) { result.append(formattedText.substring(bufferPositionFormattedText, bufferPositionFormattedText+1)); bufferPositionFormattedText++; } } protected boolean isSingleLineCommentNext(String str, int position) { if ((str.charAt(position) == '/') && (str.charAt(position) == '/')) return true; return false; } protected boolean isMultiLineCommentNext(String str, int position) { if ((str.charAt(position) == '/') && (str.charAt(position) == '*')) return true; return false; } protected boolean isCommentNext(String str, int position) { if (isSingleLineCommentNext(str, position) || isMultiLineCommentNext(str, position)) return true; else return false; } protected String scanBackWhitespaces(String str, int position) { StringBuffer whiteSpaces = new StringBuffer(); int currentPosition = position; if (currentPosition >= str.length() || currentPosition < 0) return ""; char ch; ch = str.charAt(currentPosition); while (isWhitespace(ch)) { whiteSpaces.append(str.charAt(currentPosition)); currentPosition--; if (currentPosition < 0) break; ch = str.charAt(currentPosition); } return whiteSpaces.toString(); } protected String stripEndingNewline(String str) { int position = str.length() - 1; while ((str.charAt(position) == '\n') || (str.charAt(position) == '\r')) { position--; } return str.substring(0, position + 1); } protected int scanBackNewlinesCount(String str, int position) { int newLinesCount = 0; int currentPosition = position; while ((str.charAt(currentPosition) == '\n') || (str.charAt(currentPosition) == '\r')) { if (str.charAt(currentPosition) == '\n') { if (str.charAt(currentPosition - 1) == '\r') { currentPosition -= 2; } else { currentPosition -= 1; } newLinesCount++; } else if (str.charAt(currentPosition) == '\r') { currentPosition -= 1; newLinesCount++; } } return newLinesCount; } protected void weaveComments() { bufferPositionOriginalText = 0; bufferPositionFormattedTextNoWhitespaces = 0; bufferPositionFormattedText = 0; StringBuffer result = new StringBuffer(); formattedTextNoWhitespaces = buf.toString().replaceAll("[ \t\n\r]", ""); formattedText = buf.toString(); while (bufferPositionFormattedTextNoWhitespaces <= formattedTextNoWhitespaces.length()) { skipWhitespacesOriginalText(); skipWhitespacesFormattedText(result); if (!(bufferPositionOriginalText < originalText.length())) break; char formattedPositionNoWhitespaces; if (bufferPositionFormattedTextNoWhitespaces == formattedTextNoWhitespaces.length()) { formattedPositionNoWhitespaces = ' '; } else { formattedPositionNoWhitespaces = formattedTextNoWhitespaces.charAt(bufferPositionFormattedTextNoWhitespaces); } char originalPosition = originalText.charAt(bufferPositionOriginalText); if (formattedPositionNoWhitespaces != originalPosition) { if (formattedPositionNoWhitespaces == ';') { // formatted text always outputs the optional semicolon, skip it if necessary bufferPositionFormattedTextNoWhitespaces++; bufferPositionFormattedText++; } else if (isCommentNext(originalText, bufferPositionOriginalText)) { LeafNode currentComment = allComments.get(allCommentsPosition); if (currentComment.getTotalOffset() == bufferPositionOriginalText) { if (isMultiLineComment(currentComment.getText())) { int newLinesCount = scanBackNewlinesCount(originalText, bufferPositionOriginalText-1); if (newLinesCount > 0) { if (scanBackNewlinesCount(result.toString(), result.toString().length()-1) == 0) { result.append("\n\n"); } result.append(currentComment.getText()); result.append("\n"); } else { String lastWhiteSpaces = scanBackWhitespaces(result.toString(), result.toString().length()-1); result.delete(result.toString().length() - lastWhiteSpaces.length(), result.toString().length()); result.append(" " + stripEndingNewline(currentComment.getText())); result.append(lastWhiteSpaces); } } else if (isSingleLineComment(currentComment.getText())) { int newLinesCount = scanBackNewlinesCount(originalText, bufferPositionOriginalText-1); String lastWhiteSpaces = scanBackWhitespaces(result.toString(), result.toString().length()-1); result.delete(result.toString().length() - lastWhiteSpaces.length(), result.toString().length()); if (newLinesCount > 0) { result.append("\n\n" + stripEndingNewline(currentComment.getText())); } else { result.append(" " + stripEndingNewline(currentComment.getText())); } result.append(lastWhiteSpaces); } bufferPositionOriginalText+=currentComment.getLength(); allCommentsPosition++; } } else { // disaster handling: return original unformatted text! System.err.println("Disaster Recovery: returning original text!!"); buf = new StringBuffer(); buf.append(originalText); return; } } else { result.append(formattedText.substring(bufferPositionFormattedText, bufferPositionFormattedText+1)); bufferPositionOriginalText++; bufferPositionFormattedText++; bufferPositionFormattedTextNoWhitespaces++; } } buf = result; } protected void newLine() { buf.append("\n"); if ((ruleSpacingStack != null) && (!ruleSpacingStack.empty())) { newLineOffsetCounter = ruleSpacingStack.peek(); } else { newLineOffsetCounter = 0; } } protected void text(String str) { buf.append(str); newLineOffsetCounter += str.length(); } protected void space() { buf.append(" "); newLineOffsetCounter++; } protected void spaces(int count) { for (int i=0; i < count; i++) { buf.append(" "); } } protected boolean lastIsClosingParentheses() { char ch = buf.toString().charAt(buf.toString().length()-1); if ((ch == ')') || (ch == ']') || (ch == '}')) return true; return false; } protected void wrap() { if ((config.isWrapAfterThreshold()) && (newLineOffsetCounter > config.getWrapThreshold())) { char last = buf.toString().charAt(buf.toString().length()-1); if (!((last == '(' || last == '[' || last == '{' ))) { newLine(); if (ruleSpacingStack.size() > 1) spaces(ruleSpacingStack.peek() + 1); else spaces(ruleSpacingStack.peek()); } } } // ----------------------------------------------------------------------------- protected void visitBefore(EtsiBnf node) { parserEtsiBnfNode = NodeUtil.getNodeAdapter(node).getParserNode(); collectAllComments(parserEtsiBnfNode); originalText = NodeUtil.getNodeAdapter(node).getParserNode().serialize(); originalTextNoWhitespaces = originalText.replaceAll("[ \t\n\r]", ""); text("grammar " + node.getName()); if (node.getType() != null) text(node.getType()); text(";"); newLine(); newLine(); } protected void visitAfter(EtsiBnf node) { weaveComments(); } protected void visitBefore(ImportSection node) { } protected void visitAfter(ImportSection node) { newLine(); } protected void visitBefore(BnfEntry node) { } protected void visitAfter(BnfEntry node) { } protected void visitBefore(DeltaEntry node) { } protected void visitAfter(DeltaEntry node) { } protected void visitBefore(MergeEntry node) { } protected void visitAfter(MergeEntry node) { } protected void visitBefore(Atom node) { } protected void visitAfter(Atom node) { } protected void visitBefore(Term node) { } protected void visitAfter(Term node) { if (!isLastElement()) space(); } protected void visitBefore(DefinitionList node) { averageSingleDefinitionLength = null; int totalLength = 0; for (int i=0; i < node.eContents().size(); i++) { CompositeNode parseNode = NodeUtil.getNodeAdapter(node.eContents().get(i)).getParserNode(); totalLength += parseNode.serialize().trim().length(); } averageSingleDefinitionLength = (double) totalLength / (double) node.eContents().size(); } protected void visitAfter(DefinitionList node) { } protected void visitBefore(ExtRule node) { } protected void visitAfter(ExtRule node) { } protected void visitBefore(GlobalCombinator node) { } protected void visitAfter(GlobalCombinator node) { } protected void visitBefore(HookCombinator node) { } protected void visitAfter(HookCombinator node) { } protected void visitBefore(Import node) { text("import \"" + node.getImportURI() + "\""); if (node.getGrammarType() != null) { text("/" + node.getGrammarType()); } if (node.getLabel() != null) { space(); text("label: " + node.getLabel()); } text(";"); newLine(); } protected void visitAfter(Import node) { } protected void visitBefore(MergeRule node) { } protected void visitAfter(MergeRule node) { } protected void visitBefore(GroupedSequence node) { wrap(); text("("); ruleSpacingStack.push(newLineOffsetCounter-1); } protected void visitAfter(GroupedSequence node) { // if ((config.isAlignParentheses() && (node.eContents().get(0).eContents().size() >= config.getAlignParenthesesElementCountThreshold())) || (lastIsClosingParentheses())) { if (config.isAlignParentheses() && (node.eContents().get(0).eContents().size() >= config.getAlignParenthesesElementCountThreshold())) { newLine(); spaces(ruleSpacingStack.peek()); } text(")"); ruleSpacingStack.pop(); } protected void visitBefore(OptionalSequence node) { wrap(); text("["); ruleSpacingStack.push(newLineOffsetCounter-1); } protected void visitAfter(OptionalSequence node) { // if ((config.isAlignParentheses() && (node.eContents().get(0).eContents().size() >= config.getAlignParenthesesElementCountThreshold())) || (lastIsClosingParentheses())) { if (config.isAlignParentheses() && (node.eContents().get(0).eContents().size() >= config.getAlignParenthesesElementCountThreshold())) { newLine(); spaces(ruleSpacingStack.peek()); } text("]"); ruleSpacingStack.pop(); } protected void visitBefore(RepeatedSequence node) { wrap(); text("{"); ruleSpacingStack.push(newLineOffsetCounter-1); } protected void visitAfter(RepeatedSequence node) { // if ((config.isAlignParentheses() && (node.eContents().get(0).eContents().size() >= config.getAlignParenthesesElementCountThreshold())) || (lastIsClosingParentheses())) { if (config.isAlignParentheses() && (node.eContents().get(0).eContents().size() >= config.getAlignParenthesesElementCountThreshold())) { newLine(); spaces(ruleSpacingStack.peek()); } text("}"); if (node.isMorethanonce()) text("+"); ruleSpacingStack.pop(); } protected void visitBefore(Rule node) { if (lastWasSectionHeading) newLine(); lastWasSectionHeading=false; newLineOffsetCounter = 0; if (node.getRulenumber() > 0) text(node.getRulenumber() + ". "); text(node.getName() + " ::= "); rightHandSideRuleOffset = newLineOffsetCounter; ruleSpacingStack.push(newLineOffsetCounter); } protected void visitAfter(Rule node) { text(";"); newLine(); ruleSpacingStack.pop(); } protected void visitBefore(RuleCombinator node) { } protected void visitAfter(RuleCombinator node) { } protected void visitBefore(RuleReference node) { wrap(); text(node.getRuleref().getName()); } protected void visitAfter(RuleReference node) { } protected void visitBefore(SectionHeading node) { if (!lastWasSectionHeading && !buf.substring(buf.length()-2).equals("\n\n")) newLine(); lastWasSectionHeading=true; text(node.getSectionHeader()); } protected void visitAfter(SectionHeading node) { } protected void visitBefore(SingleDefinition node) { } protected void visitAfter(SingleDefinition node) { boolean preventAlternativeBreakShortAlternatives = config.isPreventNewLineAfterAlternativeOnShortAlternatives() && (averageSingleDefinitionLength <= config.getShortAlternativeThreshold()); if (!isLastElement()) { text(" | "); if (config.isNewLineAfterAlternative()) { if (config.isPreventNewLineAfterAlternativeOnLessThanThreeElements()) { DefinitionList definitionList = (DefinitionList) node.eContainer(); if ((definitionList.eContents().size() > 2) && (!preventAlternativeBreakShortAlternatives)) { newLine(); if (ruleSpacingStack.size() > 1) spaces(ruleSpacingStack.peek() + 1); else spaces(ruleSpacingStack.peek()); } } else { if (!preventAlternativeBreakShortAlternatives) { newLine(); if (ruleSpacingStack.size() > 1) spaces(ruleSpacingStack.peek() + 1); else spaces(ruleSpacingStack.peek()); } } } } } protected void visitBefore(StringRule node) { wrap(); if (node.getLiteral() != null) text("\"" + node.getLiteral() + "\""); else if (node.getColon() != null) text("\"\"\""); } protected void visitAfter(StringRule node) { } }