package com.yoursway.rails.commons; import org.eclipse.core.runtime.Assert; import org.eclipse.dltk.ast.ASTNode; import org.eclipse.dltk.ast.declarations.MethodDeclaration; import org.eclipse.dltk.ast.declarations.ModuleDeclaration; import org.eclipse.dltk.ast.declarations.TypeDeclaration; import org.eclipse.dltk.ast.expressions.CallExpression; import org.eclipse.dltk.ruby.ast.RubySingletonMethodDeclaration; /** * A Ruby AST visitor, differing from the DLTK's visitor in four ways: * <ol> * <li>Contains typed enter methods for every intersting node types (please add * additional methods when more node types are needed).</li> * <li>Enter methods return a visitor that will be used to visit the children * and will be called when traversing of the children has finished, or * <code>null</code> to skip visiting children. * <li>There is a single <code>leave</code> method that is called on the * children visitor (i.e. the one returned from the enter method) when * traversing leaves the first node it has visited; if the enter method returns * <code>null</code> or <code>this</code> (or any other existing visitor), * no leave method is called.</li> * <li>Enter/leave methods do not throw checked exceptions.</li> * </ol> * * <p> * {@link RubyAstTraverser} should be used to traverse DLTK AST nodes using this * visitor. * </p> * * <p> * If you think you need an ability to track leaving from interim nodes, please * think again. And if you still aren't convinced, feel free to implement that. * </p> * * @author Andrey Tarantsov */ public class RubyAstVisitor<T extends ASTNode> { private final RubyAstVisitor<?> parentVisitor; private final T initialNode; private ASTNode currentNode; @SuppressWarnings("unchecked") public RubyAstVisitor(RubyAstVisitor<?> parentVisitor) { this.parentVisitor = parentVisitor; this.initialNode = (parentVisitor == null ? null : (T) parentVisitor.getCurrentNode()); } void $verify(RubyAstVisitor<?> parentVisitor, ASTNode initialNode) { if (parentVisitor == this) return; Assert.isTrue(this.parentVisitor == parentVisitor); Assert.isTrue(this.initialNode == initialNode); } protected RubyAstVisitor<?> enterNode(ASTNode node) { return this; } protected RubyAstVisitor<?> enterMethodDeclaration(MethodDeclaration node) { return enterNode(node); } protected RubyAstVisitor<?> enterSingletonMethodDeclaration(RubySingletonMethodDeclaration node) { return enterNode(node); } protected RubyAstVisitor<?> enterCall(CallExpression node) { return enterNode(node); } protected RubyAstVisitor<?> enterModuleDeclaration(ModuleDeclaration node) { return enterNode(node); } // copy this method to support a new node protected RubyAstVisitor<?> enter(MethodDeclaration node) { return enterNode(node); } protected RubyAstVisitor<?> enterTypeDeclaration(TypeDeclaration node) { return enterNode(node); } protected RubyAstVisitor<?> enterUnknown(ASTNode node) { return enterNode(node); } public final RubyAstVisitor<?> switchEnter(ASTNode node) { currentNode = node; try { if (node instanceof RubySingletonMethodDeclaration) return enterSingletonMethodDeclaration((RubySingletonMethodDeclaration) node); else if (node instanceof MethodDeclaration) return enterMethodDeclaration((MethodDeclaration) node); else if (node instanceof CallExpression) return enterCall((CallExpression) node); else if (node instanceof ModuleDeclaration) return enterModuleDeclaration((ModuleDeclaration) node); // copy the following two lines to support a new node else if (node instanceof MethodDeclaration) return enter((MethodDeclaration) node); else if (node instanceof TypeDeclaration) return enterTypeDeclaration((TypeDeclaration) node); else return enterUnknown(node); } finally { currentNode = null; } } public final RubyAstVisitor<?> leaveNode(ASTNode node) { // assert: (initialNode != null) => (node != null) Assert.isTrue(initialNode == null || node != null); if (node == initialNode) { leave(); return parentVisitor; } return this; } protected void leave() { } public RubyAstVisitor<?> getParentVisitor() { return parentVisitor; } /** * Returns the node processing of which has resulted in the creation of this * visitor. */ protected T node() { return initialNode; } private ASTNode getCurrentNode() { if (currentNode == null) throw new IllegalStateException("getCurrentNode() can only be called from inside enter*()"); return currentNode; } }