/*******************************************************************************
* Copyright (c) Gil Barash - chookapp@yahoo.com
* 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:
* Gil Barash - initial API and implementation
*******************************************************************************/
package com.chookapp.org.bracketeer.cdt;
import java.util.EmptyStackException;
import java.util.Stack;
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTBreakStatement;
import org.eclipse.cdt.core.dom.ast.IASTCaseStatement;
import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTCompoundStatement;
import org.eclipse.cdt.core.dom.ast.IASTContinueStatement;
import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTDefaultStatement;
import org.eclipse.cdt.core.dom.ast.IASTDoStatement;
import org.eclipse.cdt.core.dom.ast.IASTExpression;
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
import org.eclipse.cdt.core.dom.ast.IASTForStatement;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.IASTIfStatement;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTParameterDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTStatement;
import org.eclipse.cdt.core.dom.ast.IASTSwitchStatement;
import org.eclipse.cdt.core.dom.ast.IASTWhileStatement;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTIfStatement;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamedTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTQualifiedName;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateId;
import org.eclipse.jface.text.BadLocationException;
import com.chookapp.org.bracketeer.common.BracketsPair;
import com.chookapp.org.bracketeer.common.Hint;
import com.chookapp.org.bracketeer.common.IBracketeerProcessingContainer;
import com.chookapp.org.bracketeer.common.IHintConfiguration;
import com.chookapp.org.bracketeer.common.MutableBool;
public class ClosingBracketHintVisitor extends ASTVisitor
{
class ScopeInfo
{
public String _str;
public int _offset;
public IASTStatement _statement;
public ScopeInfo(String str, int offset, IASTStatement statement)
{
_str = str;
_offset = offset;
_statement = statement;
}
}
public class ScopeTraceException extends Exception
{
private static final long serialVersionUID = 6297837237586982280L;
public ScopeTraceException(String message)
{
super(message);
}
}
MutableBool _cancelProcessing;
IBracketeerProcessingContainer _container;
IHintConfiguration _hintConf;
Stack<ScopeInfo> _scopeStack;
public ClosingBracketHintVisitor(IBracketeerProcessingContainer container,
MutableBool cancelProcessing,
IHintConfiguration hintConf)
{
_cancelProcessing = cancelProcessing;
_container = container;
_hintConf = hintConf;
shouldVisitStatements = true;
shouldVisitDeclarations = true;
shouldVisitExpressions = true; // not really visiting expressions, see bug 370637.
_scopeStack = new Stack<ClosingBracketHintVisitor.ScopeInfo>();
}
@Override
public int leave(IASTStatement statement)
{
try
{
if( (statement instanceof IASTSwitchStatement) ||
(statement instanceof IASTForStatement) ||
(statement instanceof IASTWhileStatement) ||
(statement instanceof IASTDoStatement) )
{
ScopeInfo scope;
if( statement instanceof IASTSwitchStatement )
{
scope = _scopeStack.peek();
if((scope._statement instanceof IASTCaseStatement) ||
(scope._statement instanceof IASTDefaultStatement))
{
_scopeStack.pop();
}
}
scope = _scopeStack.pop();
if(!scope._statement.getClass().equals(statement.getClass()))
{
if(Activator.DEBUG)
{
Activator.log("Lost track of scope. Expected:" + scope._statement + //$NON-NLS-1$
" but was:" + statement); //$NON-NLS-1$
}
}
}
}
catch(EmptyStackException e)
{
if(Activator.DEBUG)
Activator.log(e);
}
return super.leave(statement);
}
@Override
public int visit(IASTStatement statement)
{
try
{
if( statement instanceof IASTIfStatement )
visitIf((IASTIfStatement) statement);
if( statement instanceof IASTSwitchStatement )
visitSwitch((IASTSwitchStatement) statement);
if( statement instanceof IASTForStatement )
visitFor((IASTForStatement) statement);
if( statement instanceof IASTWhileStatement )
visitWhile((IASTWhileStatement) statement);
if( statement instanceof IASTDoStatement )
visitDo((IASTDoStatement) statement);
if( statement instanceof IASTCaseStatement || statement instanceof IASTDefaultStatement )
visitCase(statement);
if( statement instanceof IASTBreakStatement || statement instanceof IASTContinueStatement )
visitBreak(statement);
}
catch(BadLocationException e)
{
_cancelProcessing.set(true);
}
catch(Exception e)
{
if(!(e instanceof ScopeTraceException || e instanceof EmptyStackException))
Activator.log(e);
else if(Activator.DEBUG)
Activator.log(e);
}
return shouldContinue();
}
private void visitBreak(IASTStatement statement) throws ScopeTraceException, BadLocationException
{
if(_scopeStack.isEmpty())
throw new ScopeTraceException("break without scope: " + statement); //$NON-NLS-1$
ScopeInfo scope = _scopeStack.peek();
String hintType;
if( scope._statement instanceof IASTForStatement )
hintType = "break-for"; //$NON-NLS-1$
else if( scope._statement instanceof IASTWhileStatement )
hintType = "break-while"; //$NON-NLS-1$
else if( scope._statement instanceof IASTDoStatement )
hintType = "break-do"; //$NON-NLS-1$
else if( scope._statement instanceof IASTCaseStatement || scope._statement instanceof IASTDefaultStatement )
hintType = "break-case"; //$NON-NLS-1$
else
throw new ScopeTraceException("Unexpect scope ("+scope._statement+") on break/continue:" + statement); //$NON-NLS-1$ //$NON-NLS-2$
int endLoc = statement.getFileLocation().getNodeOffset() + statement.getFileLocation().getNodeLength() - 1;
_container.add(new Hint(hintType, scope._offset, endLoc, scope._str));
}
private void visitCase(IASTStatement statement) throws ScopeTraceException
{
/* TODO: specific params: don't show the switch part (only the case argument) */
ScopeInfo scope = _scopeStack.peek();
if( !(scope._statement instanceof IASTSwitchStatement) )
{
if(!((scope._statement instanceof IASTCaseStatement) ||
(scope._statement instanceof IASTDefaultStatement)) )
{
throw new ScopeTraceException("Lost track of stack (in case), found:" + scope._statement); //$NON-NLS-1$
}
_scopeStack.pop();
scope = _scopeStack.peek();
}
if( !(scope._statement instanceof IASTSwitchStatement) )
{
throw new ScopeTraceException("Lost track of stack (in case2), found:" + scope._statement); //$NON-NLS-1$
}
String hint = ""; //$NON-NLS-1$
if(statement instanceof IASTCaseStatement)
{
IASTExpression cond = ((IASTCaseStatement)statement).getExpression();
if( cond != null )
hint = cond.getRawSignature();
hint = "case: " + hint; //$NON-NLS-1$
}
else // default
{
hint = "default"; //$NON-NLS-1$
}
int startLoc = statement.getFileLocation().getNodeOffset();
_scopeStack.push(new ScopeInfo(scope._str + " - " + hint, startLoc, statement)); //$NON-NLS-1$
}
private void visitDo(IASTDoStatement statement)
{
IASTExpression cond = statement.getCondition();
String hint = ""; //$NON-NLS-1$
if( cond != null )
hint = cond.getRawSignature();
hint = "do_while( " + hint + " )"; //$NON-NLS-1$ //$NON-NLS-2$
int startLoc = statement.getFileLocation().getNodeOffset();
_scopeStack.push(new ScopeInfo(hint, startLoc, statement));
}
private void visitIf(IASTIfStatement statement) throws BadLocationException
{
/* TODO: specific params: don't show the if hint if there's an "else if" after it (by checking if the elseClause is an instance of ifstatment) */
String hint = ""; //$NON-NLS-1$
if( statement.getConditionExpression() != null )
{
hint = statement.getConditionExpression().getRawSignature();
}
else
{
if( (statement instanceof ICPPASTIfStatement) &&
((ICPPASTIfStatement)statement).getConditionDeclaration() != null )
{
hint = ((ICPPASTIfStatement)statement).getConditionDeclaration().getRawSignature();
}
}
IASTStatement thenClause = statement.getThenClause();
IASTStatement elseClause = statement.getElseClause();
boolean showIfHint = (elseClause == null);
int endLoc = -1;
if( !showIfHint )
{
if (elseClause.getFileLocation().getStartingLineNumber() !=
thenClause.getFileLocation().getEndingLineNumber() )
{
showIfHint = true;
}
// if the else looks like this "} else {", then show the hint on the "{"
if( !showIfHint && !(elseClause instanceof IASTIfStatement) )
{
endLoc = elseClause.getFileLocation().getNodeOffset();
showIfHint = true;
}
}
if( showIfHint && !(thenClause instanceof IASTCompoundStatement) )
showIfHint = false;
if( showIfHint )
{
IASTFileLocation location = thenClause.getFileLocation();
if( endLoc == -1 )
endLoc = location.getNodeOffset()+location.getNodeLength()-1;
int startLoc = statement.getFileLocation().getNodeOffset();
_container.add(new Hint("if", startLoc, endLoc, "if( "+hint+" )")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
if( elseClause != null && !(elseClause instanceof IASTIfStatement) &&
(elseClause instanceof IASTCompoundStatement))
{
IASTFileLocation location = elseClause.getFileLocation();
endLoc = location.getNodeOffset()+location.getNodeLength()-1;
int startLoc = location.getNodeOffset();
_container.add(new Hint("if", startLoc, endLoc, "else_of_if( "+hint+" )")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
private void visitSwitch(IASTSwitchStatement statement) throws BadLocationException
{
String hint = statement.getControllerExpression().getRawSignature();
IASTFileLocation location = statement.getBody().getFileLocation();
int endLoc = location.getNodeOffset()+location.getNodeLength()-1;
int startLoc = statement.getFileLocation().getNodeOffset();
hint = "switch( "+hint+" )"; //$NON-NLS-1$ //$NON-NLS-2$
_container.add(new Hint("switch", startLoc, endLoc, hint)); //$NON-NLS-1$
_scopeStack.push(new ScopeInfo(hint, startLoc, statement));
}
private void visitFor(IASTForStatement statement) throws BadLocationException
{
/* TODO: specific params: show also initializer && increment expressions */
IASTExpression cond = statement.getConditionExpression();
String hint = ""; //$NON-NLS-1$
if( cond != null )
hint = cond.getRawSignature();
hint = "for( "+hint+" )"; //$NON-NLS-1$ //$NON-NLS-2$
int startLoc = statement.getFileLocation().getNodeOffset();
_scopeStack.push(new ScopeInfo(hint, startLoc, statement));
IASTStatement body = statement.getBody();
if( body instanceof IASTCompoundStatement)
{
IASTFileLocation location = body.getFileLocation();
int endLoc = location.getNodeOffset()+location.getNodeLength()-1;
_container.add(new Hint("for", startLoc, endLoc, hint)); //$NON-NLS-1$
}
}
private void visitWhile(IASTWhileStatement statement) throws BadLocationException
{
IASTExpression cond = statement.getCondition();
String hint = ""; //$NON-NLS-1$
if( cond != null )
hint = cond.getRawSignature();
hint = "while( "+hint+" )"; //$NON-NLS-1$ //$NON-NLS-2$
int startLoc = statement.getFileLocation().getNodeOffset();
_scopeStack.push(new ScopeInfo(hint, startLoc, statement));
IASTStatement body = statement.getBody();
if( body instanceof IASTCompoundStatement)
{
IASTFileLocation location = body.getFileLocation();
int endLoc = location.getNodeOffset()+location.getNodeLength()-1;
_container.add(new Hint("while", startLoc, endLoc, hint)); //$NON-NLS-1$
}
}
@Override
public int visit(IASTDeclaration declaration)
{
try
{
if( declaration instanceof IASTFunctionDefinition )
visitFunc((IASTFunctionDefinition) declaration);
if( declaration instanceof IASTSimpleDeclaration)
visitType((IASTSimpleDeclaration) declaration);
}
catch (Exception e)
{
Activator.log(e);
}
return shouldContinue();
}
private void visitFunc(IASTFunctionDefinition declaration) throws BadLocationException
{
IASTStatement body = declaration.getBody();
if(!( body instanceof IASTCompoundStatement) )
return;
// starting a function empties the stack... (which should already be empty on good flow)
_scopeStack.clear();
IASTFileLocation location = body.getFileLocation();
int endLoc = location.getNodeOffset()+location.getNodeLength()-1;
IASTFunctionDeclarator declerator = declaration.getDeclarator();
int startLoc = declerator.getFileLocation().getNodeOffset();
StringBuffer hint = new StringBuffer();
hint.append(declerator.getName().getRawSignature());
/* TODO: specific params: exclude function parameters (show only the name) */
hint.append("( "); //$NON-NLS-1$
IASTNode[] decChildren = declerator.getChildren();
boolean firstParam = true;
for (int i = 0; i < decChildren.length; i++)
{
IASTNode node = decChildren[i];
if( node instanceof IASTParameterDeclaration)
{
IASTParameterDeclaration param = (IASTParameterDeclaration) node;
if( firstParam )
firstParam = false;
else
hint.append(", "); //$NON-NLS-1$
hint.append(param.getDeclarator().getName());
}
}
hint.append(" )"); //$NON-NLS-1$
_container.add(new Hint("function", startLoc, endLoc, hint.toString())); //$NON-NLS-1$
}
private void visitType(IASTSimpleDeclaration declaration) throws BadLocationException
{
/* TODO: specific params: include type('class' / 'struct') */
IASTDeclSpecifier spec = declaration.getDeclSpecifier();
if( spec instanceof IASTCompositeTypeSpecifier )
{
String hint = ((IASTCompositeTypeSpecifier)spec).getName().getRawSignature();
if( hint.isEmpty() )
return;
IASTFileLocation location = declaration.getFileLocation();
int endLoc = location.getNodeOffset()+location.getNodeLength()-1;
int startLoc = location.getNodeOffset();
_container.add(new Hint("type", startLoc, endLoc, hint)); //$NON-NLS-1$
}
if(spec instanceof ICPPASTNamedTypeSpecifier)
{
IASTName name = ((ICPPASTNamedTypeSpecifier) spec).getName();
addBrackets(name);
}
}
private void addBrackets(IASTName name) throws BadLocationException
{
if( name instanceof ICPPASTTemplateId )
{
IASTNode[] args = ((ICPPASTTemplateId) name).getTemplateArguments();
addBrackets(args);
}
else if( name instanceof ICPPASTQualifiedName)
{
IASTName[] names = ((ICPPASTQualifiedName) name).getNames();
for (IASTName n : names)
addBrackets(n);
}
}
private void addBrackets(IASTNode[] args) throws BadLocationException
{
if(args == null || args.length == 0)
return;
int startLoc = args[0].getFileLocation().getNodeOffset() - 1;
IASTFileLocation endFileLoc = args[args.length-1].getFileLocation();
int endLoc = endFileLoc.getNodeOffset() + endFileLoc.getNodeLength();
_container.add(new BracketsPair(startLoc, '<', endLoc, '>'));
}
private int shouldContinue()
{
if( _cancelProcessing.get() )
return PROCESS_ABORT;
else
return PROCESS_CONTINUE;
}
}