/******************************************************************************* * Copyright (c) 2008, 2016 xored software, Inc. and others. * * 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 * * Contributors: * xored software, Inc. - initial API and Implementation (Alex Panchenko) *******************************************************************************/ package org.eclipse.dltk.ruby.formatter.internal; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import org.eclipse.dltk.formatter.AbstractFormatterNodeBuilder; import org.eclipse.dltk.formatter.IFormatterContainerNode; import org.eclipse.dltk.formatter.IFormatterDocument; import org.eclipse.dltk.formatter.IFormatterTextNode; import org.eclipse.dltk.ruby.core.RubyConstants; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterArrayNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterAtBeginNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterAtEndNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterBeginNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterCaseNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterClassNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterDoNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterElseIfNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterEnsureNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterForNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterHashNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterHereDocNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterIfElseNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterIfEndNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterIfNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterMethodNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterModifierNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterModuleNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterRDocNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterRequireNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterRescueElseNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterRescueNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterRootNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterStringNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterUntilNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterWhenElseNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterWhenNode; import org.eclipse.dltk.ruby.formatter.internal.nodes.FormatterWhileNode; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Region; import org.eclipse.osgi.util.NLS; import org.jruby.ast.ArgumentNode; import org.jruby.ast.ArrayNode; import org.jruby.ast.BeginNode; import org.jruby.ast.CaseNode; import org.jruby.ast.ClassNode; import org.jruby.ast.CommentNode; import org.jruby.ast.DRegexpNode; import org.jruby.ast.DStrNode; import org.jruby.ast.DefnNode; import org.jruby.ast.DefsNode; import org.jruby.ast.EnsureNode; import org.jruby.ast.FCallNode; import org.jruby.ast.ForNode; import org.jruby.ast.HashNode; import org.jruby.ast.IfNode; import org.jruby.ast.IterNode; import org.jruby.ast.ListNode; import org.jruby.ast.MethodDefNode; import org.jruby.ast.ModuleNode; import org.jruby.ast.Node; import org.jruby.ast.PostExeNode; import org.jruby.ast.RegexpNode; import org.jruby.ast.RescueBodyNode; import org.jruby.ast.RescueNode; import org.jruby.ast.SClassNode; import org.jruby.ast.StrNode; import org.jruby.ast.UntilNode; import org.jruby.ast.WhenNode; import org.jruby.ast.WhileNode; import org.jruby.ast.XStrNode; import org.jruby.ast.ext.ElseNode; import org.jruby.ast.ext.HeredocNode; import org.jruby.ast.ext.PreExeNode; import org.jruby.ast.visitor.AbstractVisitor; import org.jruby.evaluator.Instruction; import org.jruby.lexer.yacc.ISourcePosition; import org.jruby.lexer.yacc.ISourcePositionHolder; import org.jruby.parser.RubyParserResult; public class RubyFormatterNodeBuilder extends AbstractFormatterNodeBuilder { public IFormatterContainerNode build(RubyParserResult result, final IFormatterDocument document) { final IFormatterContainerNode root = new FormatterRootNode(document); start(root); result.getAST().accept(new AbstractVisitor() { @Override protected Instruction visitNode(Node visited) { visitChildren(visited); return null; } @Override public Instruction visitClassNode(ClassNode visited) { FormatterClassNode classNode = new FormatterClassNode(document); classNode.setBegin(createTextNode(document, visited .getClassKeyword())); push(classNode); visitChildren(visited); checkedPop(classNode, visited.getEnd().getPosition() .getStartOffset()); classNode.setEnd(createTextNode(document, visited.getEnd())); return null; } @Override public Instruction visitSClassNode(SClassNode visited) { FormatterClassNode classNode = new FormatterClassNode(document); classNode.setBegin(createTextNode(document, visited .getClassKeyword())); push(classNode); visitChildren(visited); checkedPop(classNode, visited.getEnd().getPosition() .getStartOffset()); classNode.setEnd(createTextNode(document, visited.getEnd())); return null; } @Override public Instruction visitModuleNode(ModuleNode visited) { FormatterModuleNode moduleNode = new FormatterModuleNode( document); moduleNode.setBegin(createTextNode(document, visited .getKeyword())); push(moduleNode); visitChildren(visited); checkedPop(moduleNode, visited.getEnd().getPosition() .getStartOffset()); moduleNode.setEnd(createTextNode(document, visited.getEnd())); return null; } @Override public Instruction visitDefnNode(DefnNode visited) { return visitMethodDefNode(visited); } @Override public Instruction visitDefsNode(DefsNode visited) { return visitMethodDefNode(visited); } private Instruction visitMethodDefNode(MethodDefNode visited) { FormatterMethodNode methodNode = new FormatterMethodNode( document); methodNode .setBegin(createTextNode(document, visited .getStartOffset(), visited.getNameNode() .getEndOffset())); push(methodNode); visitChildren(visited); checkedPop(methodNode, visited.getEnd().getPosition() .getStartOffset()); methodNode.setEnd(createTextNode(document, visited.getEnd())); return null; } @Override public Instruction visitWhileNode(WhileNode visited) { if (!visited.isBlock()) { visitChildren(visited); return null; } FormatterWhileNode whileNode = new FormatterWhileNode(document); whileNode.setBegin(createTextNode(document, visited .getKeyword())); push(whileNode); visitChildren(visited); checkedPop(whileNode, visited.getEnd().getPosition() .getStartOffset()); whileNode.setEnd(createTextNode(document, visited.getEnd())); return null; } @Override public Instruction visitIterNode(IterNode visited) { FormatterDoNode forNode = new FormatterDoNode(document); forNode.setBegin(createTextNode(document, visited.getBegin())); push(forNode); visitChildren(visited); checkedPop(forNode, visited.getEnd().getPosition() .getStartOffset()); forNode.setEnd(createTextNode(document, visited.getEnd())); return null; } @Override public Instruction visitForNode(ForNode visited) { FormatterForNode forNode = new FormatterForNode(document); forNode.setBegin(createTextNode(document, visited.getBegin())); push(forNode); visitChildren(visited); checkedPop(forNode, visited.getEnd().getPosition() .getStartOffset()); forNode.setEnd(createTextNode(document, visited.getEnd())); return null; } @Override public Instruction visitUntilNode(UntilNode visited) { if (!visited.isBlock()) { visitChild(visited.getBodyNode()); FormatterModifierNode block = new FormatterModifierNode( document); block.addChild(createTextNode(document, visited .getKeyword())); push(block); visitChild(visited.getConditionNode()); checkedPop(block, visited.getConditionNode().getEndOffset()); return null; } FormatterUntilNode untilNode = new FormatterUntilNode(document); untilNode.setBegin(createTextNode(document, visited .getKeyword())); push(untilNode); visitChildren(visited); checkedPop(untilNode, visited.getEnd().getPosition() .getStartOffset()); untilNode.setEnd(createTextNode(document, visited.getEnd())); return null; } @Override public Instruction visitCaseNode(CaseNode visited) { FormatterCaseNode caseNode = new FormatterCaseNode(document); final int caseEnd = visited.getCaseKeyword().getPosition() .getEndOffset(); caseNode.setBegin(createTextNode(document, visited .getStartOffset(), caseEnd)); push(caseNode); Node branch = visited.getFirstWhenNode(); while (branch != null) { if (branch instanceof WhenNode) { WhenNode whenBranch = (WhenNode) branch; FormatterWhenNode whenNode = new FormatterWhenNode( document); whenNode.setBegin(createTextNode(document, branch .getStartOffset(), whenBranch .getExpressionNodes().getEndOffset())); push(whenNode); visitChild(whenBranch.getBodyNode()); branch = ((WhenNode) branch).getNextCase(); checkedPop(whenNode, branch != null ? branch .getStartOffset() : visited.getEnd() .getPosition().getStartOffset()); } else if (branch instanceof ElseNode) { ElseNode elseBranch = (ElseNode) branch; FormatterWhenElseNode whenElseNode = new FormatterWhenElseNode( document); whenElseNode.setBegin(createTextNode(document, elseBranch.getStartOffset(), elseBranch .getElseKeyword().getPosition() .getEndOffset())); push(whenElseNode); visitChild(elseBranch.getStatement()); checkedPop(whenElseNode, visited.getEnd().getPosition() .getStartOffset()); branch = null; } else { RubyFormatterPlugin.warn(NLS.bind( Messages.RubyFormatterNodeBuilder_unexpectedClassInCaseWhenExpression, branch.getClass().getName()), new DumpStackOnly()); break; } } checkedPop(caseNode, visited.getEnd().getPosition() .getStartOffset()); caseNode.setEnd(createTextNode(document, visited.getEnd())); return null; } @Override public Instruction visitCommentNode(CommentNode visited) { FormatterRDocNode commentNode = new FormatterRDocNode(document, visited.getStartOffset(), visited.getEndOffset()); addChild(commentNode); return null; } @Override public Instruction visitIfNode(IfNode visited) { if (visited.isInline()) { List<Node> children = new ArrayList<Node>(3); if (visited.getThenBody() != null) { children.add(visited.getThenBody()); } if (visited.getElseBody() != null) { children.add(visited.getElseBody()); } if (visited.getCondition() != null) { children.add(visited.getCondition()); } if (!children.isEmpty()) { Collections.sort(children, POSITION_COMPARATOR); visitChildren(children); } return null; } FormatterIfNode ifNode = new FormatterIfNode(document); ifNode.setBegin(createTextNode(document, visited .getStartOffset(), visited.getCondition() .getEndOffset())); push(ifNode); visitChild(visited.getFirstBody()); checkedPop(ifNode, visited.getSecondBody() != null ? visited .getSecondBody().getStartOffset() : visited .getEndKeyword().getPosition().getStartOffset()); Node branch = visited.getSecondBody(); while (branch != null) { if (branch instanceof IfNode.ElseIf) { final IfNode.ElseIf elseIfBranch = (IfNode.ElseIf) branch; FormatterElseIfNode elseIfNode = new FormatterElseIfNode( document); elseIfNode.setBegin(createTextNode(document, elseIfBranch.getStartOffset(), elseIfBranch .getCondition().getEndOffset())); push(elseIfNode); visitChild(elseIfBranch.getFirstBody()); branch = ((IfNode.ElseIf) branch).getSecondBody(); checkedPop(elseIfNode, branch != null ? branch .getStartOffset() : visited.getEndKeyword() .getPosition().getStartOffset()); } else if (branch instanceof ElseNode) { final ElseNode elseBranch = (ElseNode) branch; FormatterIfElseNode elseNode = new FormatterIfElseNode( document); elseNode.setBegin(createTextNode(document, elseBranch .getElseKeyword())); push(elseNode); visitChild(elseBranch.getStatement()); checkedPop(elseNode, visited.getEndKeyword() .getPosition().getStartOffset()); branch = null; } else { RubyFormatterPlugin.warn(NLS.bind( Messages.RubyFormatterNodeBuilder_unexpectedClassInIfExpression, branch .getClass().getName()), new DumpStackOnly()); break; } } addChild(new FormatterIfEndNode(document, visited .getEndKeyword().getPosition())); return null; } /* * @see * org.jruby.ast.visitor.AbstractVisitor#visitBeginNode(org.jruby * .ast.BeginNode) */ @Override public Instruction visitBeginNode(BeginNode visited) { FormatterBeginNode beginNode = new FormatterBeginNode(document); beginNode.setBegin(createTextNode(document, visited .getBeginKeyword())); push(beginNode); visitChild(visited.getBodyNode()); checkedPop(beginNode, visited.getEndKeyword().getPosition() .getStartOffset()); beginNode.setEnd(createTextNode(document, visited .getEndKeyword())); return null; } @Override public Instruction visitRescueNode(RescueNode visited) { if (visited.isInline()) { return null; } visitChild(visited.getBodyNode()); RescueBodyNode node = visited.getRescueNode(); while (node != null) { FormatterRescueNode rescueNode = new FormatterRescueNode( document); rescueNode.setBegin(createTextNode(document, node .getRescueKeyword().getPosition().getStartOffset(), node.getExceptionNodes() != null ? node .getExceptionNodes().getEndOffset() : node .getRescueKeyword().getPosition() .getEndOffset())); push(rescueNode); visitChild(node.getBodyNode()); node = node.getOptRescueNode(); final int rescueEnd; if (node != null) { rescueEnd = node.getStartOffset(); } else if (visited.getElseNode() != null) { rescueEnd = visited.getElseNode().getStartOffset(); } else { rescueEnd = -1; } checkedPop(rescueNode, rescueEnd); } if (visited.getElseNode() != null) { final ElseNode elseBranch = (ElseNode) visited .getElseNode(); FormatterRescueElseNode elseNode = new FormatterRescueElseNode( document); elseNode.setBegin(createTextNode(document, elseBranch .getElseKeyword())); push(elseNode); visitChild(elseBranch.getStatement()); checkedPop(elseNode, -1); } return null; } @Override public Instruction visitEnsureNode(EnsureNode visited) { visitChild(visited.getBodyNode()); FormatterEnsureNode ensureNode = new FormatterEnsureNode( document); ensureNode.setBegin(createTextNode(document, visited .getEnsureNode().getKeyword())); push(ensureNode); visitChild(visited.getEnsureNode().getStatement()); checkedPop(ensureNode, -1); return null; } @Override public Instruction visitPreExeNode(PreExeNode visited) { FormatterAtBeginNode endNode = new FormatterAtBeginNode( document); endNode.setBegin(createTextNode(document, visited.getKeyword() .getPosition().getStartOffset(), visited.getLeftBrace() .getPosition().getEndOffset())); push(endNode); visitChildren(visited); checkedPop(endNode, visited.getRightBrace().getPosition() .getStartOffset()); endNode .setEnd(createTextNode(document, visited .getRightBrace())); return null; } @Override public Instruction visitPostExeNode(PostExeNode visited) { FormatterAtEndNode endNode = new FormatterAtEndNode(document); endNode.setBegin(createTextNode(document, visited .getEndKeyword().getPosition().getStartOffset(), visited.getLeftBrace().getPosition().getEndOffset())); push(endNode); visitChildren(visited); checkedPop(endNode, visited.getRightBrace().getPosition() .getStartOffset()); endNode .setEnd(createTextNode(document, visited .getRightBrace())); return null; } @Override public Instruction visitStrNode(StrNode visited) { FormatterStringNode strNode = new FormatterStringNode(document, visited.getStartOffset(), visited.getEndOffset()); addChild(strNode); return null; } @Override public Instruction visitDStrNode(DStrNode visited) { FormatterStringNode strNode = new FormatterStringNode(document, visited.getStartOffset(), visited.getEndOffset()); addChild(strNode); return null; } @Override public Instruction visitRegexpNode(RegexpNode visited) { FormatterStringNode strNode = new FormatterStringNode(document, visited.getStartOffset(), visited.getEndOffset()); addChild(strNode); return null; } @Override public Instruction visitDRegxNode(DRegexpNode visited) { FormatterStringNode strNode = new FormatterStringNode(document, visited.getStartOffset(), visited.getEndOffset()); addChild(strNode); return null; } @Override public Instruction visitXStrNode(XStrNode visited) { FormatterStringNode strNode = new FormatterStringNode(document, visited.getStartOffset(), visited.getEndOffset()); addChild(strNode); return null; } @Override public Instruction visitHeredocNode(HeredocNode visited) { FormatterHereDocNode heredocNode = new FormatterHereDocNode( document, visited.getStartOffset(), visited .getEndOffset(), visited.isIndent()); addChild(heredocNode); heredocNode.setContentRegion(createRegion(visited.getContent() .getPosition())); heredocNode.setEndMarkerRegion(createRegion(visited .getEndMarker().getPosition())); return null; } @Override public Instruction visitFCallNode(FCallNode visited) { if (isRequireMethod(visited)) { FormatterRequireNode requireNode = new FormatterRequireNode( document, visited.getStartOffset(), visited .getEndOffset()); addChild(requireNode); return null; } else { return super.visitFCallNode(visited); } } @Override public Instruction visitArrayNode(ArrayNode visited) { if (visited.getLeftBracketPosition() != null && visited.getRightBracketPosition() != null) { final FormatterArrayNode arrayNode = new FormatterArrayNode( document); arrayNode.setBegin(createTextNode(document, visited .getLeftBracketPosition())); push(arrayNode); final ISourcePosition right = visited .getRightBracketPosition().getPosition(); checkedPop(arrayNode, right.getStartOffset()); arrayNode.setEnd(createTextNode(document, right)); return null; } else { return super.visitArrayNode(visited); } } @Override public Instruction visitHashNode(HashNode visited) { if (visited.getLeftBrace() != null && visited.getRightBrace() != null) { final FormatterHashNode hashNode = new FormatterHashNode( document); hashNode.setBegin(createTextNode(document, visited .getLeftBrace())); push(hashNode); final ISourcePosition right = visited.getRightBrace() .getPosition(); checkedPop(hashNode, right.getStartOffset()); hashNode.setEnd(createTextNode(document, right)); return null; } else { return super.visitHashNode(visited); } } private boolean isRequireMethod(FCallNode call) { if (RubyConstants.REQUIRE.equals(call.getName())) { if (call.getArgsNode() instanceof ArrayNode) { return true; } } return false; } private void visitChildren(Node visited) { final List<Node> children = visited.childNodes(); if (!children.isEmpty()) { visitChildren(children); } } private void visitChildren(List<Node> children) { for (Iterator<Node> i = children.iterator(); i.hasNext();) { final Node child = i.next(); visitChild(child); } } private void visitChild(final Node child) { if (child != null && isVisitable(child)) { child.accept(this); } } private boolean isVisitable(Node node) { return !(node instanceof ArgumentNode) && node.getClass() != ListNode.class; } }); checkedPop(root, document.getLength()); return root; } /** * @param holder * @return */ private IFormatterTextNode createTextNode(IFormatterDocument document, ISourcePositionHolder holder) { return createTextNode(document, holder.getPosition()); } /** * @param position * @return */ private IFormatterTextNode createTextNode(IFormatterDocument document, ISourcePosition position) { return createTextNode(document, position.getStartOffset(), position .getEndOffset()); } private static IRegion createRegion(ISourcePosition position) { return new Region(position.getStartOffset(), position.getEndOffset() - position.getStartOffset()); } protected static final Comparator<Node> POSITION_COMPARATOR = new Comparator<Node>() { @Override public int compare(Node node1, Node node2) { return node1.getStartOffset() - node2.getStartOffset(); } }; }