blob: 8c9765185011c0dc35353f5bfa17a44c36befac2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2017 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;
}
@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 = (node1, node2) -> node1.getStartOffset()
- node2.getStartOffset();
}