/*******************************************************************************
* Copyright (c) 2000, 2011 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.jsdt.internal.codeassist.select;
/*
* Parser able to build specific completion parse nodes, given a cursorLocation.
*
* Cursor location denotes the position of the last character behind which completion
* got requested:
* -1 means completion at the very beginning of the source
* 0 means completion behind the first character
* n means completion behind the n-th character
*/
import org.eclipse.wst.jsdt.internal.codeassist.impl.AssistParser;
import org.eclipse.wst.jsdt.internal.compiler.CompilationResult;
import org.eclipse.wst.jsdt.internal.compiler.ast.ASTNode;
import org.eclipse.wst.jsdt.internal.compiler.ast.AbstractVariableDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.AllocationExpression;
import org.eclipse.wst.jsdt.internal.compiler.ast.Argument;
import org.eclipse.wst.jsdt.internal.compiler.ast.CaseStatement;
import org.eclipse.wst.jsdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.Expression;
import org.eclipse.wst.jsdt.internal.compiler.ast.FieldReference;
import org.eclipse.wst.jsdt.internal.compiler.ast.ImportReference;
import org.eclipse.wst.jsdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.wst.jsdt.internal.compiler.ast.MessageSend;
import org.eclipse.wst.jsdt.internal.compiler.ast.NameReference;
import org.eclipse.wst.jsdt.internal.compiler.ast.QualifiedAllocationExpression;
import org.eclipse.wst.jsdt.internal.compiler.ast.Statement;
import org.eclipse.wst.jsdt.internal.compiler.ast.SwitchStatement;
import org.eclipse.wst.jsdt.internal.compiler.ast.TypeReference;
import org.eclipse.wst.jsdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.wst.jsdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.wst.jsdt.internal.compiler.parser.JavadocParser;
import org.eclipse.wst.jsdt.internal.compiler.parser.RecoveredType;
import org.eclipse.wst.jsdt.internal.compiler.problem.ProblemReporter;
import org.eclipse.wst.jsdt.internal.compiler.util.Util;
public class SelectionParser extends AssistParser {
// OWNER
protected static final int SELECTION_PARSER = 1024;
protected static final int SELECTION_OR_ASSIST_PARSER = ASSIST_PARSER + SELECTION_PARSER;
// KIND : all values known by SelectionParser are between 1025 and 1549
protected static final int K_BETWEEN_CASE_AND_COLON = SELECTION_PARSER + 1; // whether we are inside a block
public ASTNode assistNodeParent; // the parent node of assist node
/* public fields */
public int selectionStart, selectionEnd;
public static final char[] THIS = "this".toCharArray(); //$NON-NLS-1$
public SelectionParser(ProblemReporter problemReporter) {
super(problemReporter);
this.javadocParser.checkDocComment = true;
}
public char[] assistIdentifier(){
return ((SelectionScanner)scanner).selectionIdentifier;
}
protected void attachOrphanCompletionNode(){
if (isOrphanCompletionNode){
ASTNode orphan = this.assistNode;
isOrphanCompletionNode = false;
/* if in context of a type, then persists the identifier into a fake field return type */
if (currentElement instanceof RecoveredType){
RecoveredType recoveredType = (RecoveredType)currentElement;
/* filter out cases where scanner is still inside type header */
if (recoveredType.foundOpeningBrace) {
/* generate a pseudo field with a completion on type reference */
if (orphan instanceof TypeReference){
currentElement = currentElement.add(new SelectionOnFieldType((TypeReference)orphan), 0);
return;
}
}
}
if (orphan instanceof Expression) {
buildMoreCompletionContext((Expression)orphan);
} else {
Statement statement = (Statement) orphan;
currentElement = currentElement.add(statement, 0);
}
currentToken = 0; // given we are not on an eof, we do not want side effects caused by looked-ahead token
}
}
private void buildMoreCompletionContext(Expression expression) {
ASTNode parentNode = null;
int kind = topKnownElementKind(SELECTION_OR_ASSIST_PARSER);
if(kind != 0) {
// int info = topKnownElementInfo(SELECTION_OR_ASSIST_PARSER);
switch (kind) {
case K_BETWEEN_CASE_AND_COLON :
if(this.expressionPtr > 0) {
SwitchStatement switchStatement = new SwitchStatement();
switchStatement.expression = this.expressionStack[this.expressionPtr - 1];
if(this.astLengthPtr > -1 && this.astPtr > -1) {
int length = this.astLengthStack[this.astLengthPtr];
int newAstPtr = this.astPtr - length;
ASTNode firstNode = this.astStack[newAstPtr + 1];
if(length != 0 && firstNode.sourceStart > switchStatement.expression.sourceEnd) {
switchStatement.statements = new Statement[length + 1];
System.arraycopy(
this.astStack,
newAstPtr + 1,
switchStatement.statements,
0,
length);
}
}
CaseStatement caseStatement = new CaseStatement(expression, expression.sourceStart, expression.sourceEnd);
if(switchStatement.statements == null) {
switchStatement.statements = new Statement[]{caseStatement};
} else {
switchStatement.statements[switchStatement.statements.length - 1] = caseStatement;
}
parentNode = switchStatement;
this.assistNodeParent = parentNode;
}
break;
}
}
if(parentNode != null) {
currentElement = currentElement.add((Statement)parentNode, 0);
} else {
currentElement = currentElement.add((Statement)wrapWithExplicitConstructorCallIfNeeded(expression), 0);
if(lastCheckPoint < expression.sourceEnd) {
lastCheckPoint = expression.sourceEnd + 1;
}
}
}
private boolean checkRecoveredType() {
if (currentElement instanceof RecoveredType){
/* check if current awaiting identifier is the completion identifier */
if (this.indexOfAssistIdentifier() < 0) return false;
if ((lastErrorEndPosition >= selectionStart)
&& (lastErrorEndPosition <= selectionEnd+1)){
return false;
}
RecoveredType recoveredType = (RecoveredType)currentElement;
/* filter out cases where scanner is still inside type header */
if (recoveredType.foundOpeningBrace) {
this.assistNode = this.getTypeReference(0);
this.lastCheckPoint = this.assistNode.sourceEnd + 1;
this.isOrphanCompletionNode = true;
return true;
}
}
return false;
}
protected void classInstanceCreation(boolean hasClassBody, boolean isShort) {
// ClassInstanceCreationExpression ::= 'new' ClassType '(' ArgumentListopt ')' ClassBodyopt
// ClassBodyopt produces a null item on the astStak if it produces NO class body
// An empty class body produces a 0 on the length stack.....
// if ((astLengthStack[astLengthPtr] == 1)
// && (astStack[astPtr] == null)) {
// int index;
int argsLength= isShort ? 0 : expressionLengthStack[expressionLengthPtr];
if (!(this.expressionStack[this.expressionPtr-argsLength] instanceof SelectionOnSingleNameReference))
{
//
//
// if ((index = this.indexOfAssistIdentifier()) < 0) {
// super.classInstanceCreation(hasClassBody, isShort);
// return;
// } else if(this.identifierLengthPtr > -1 &&
// (this.identifierLengthStack[this.identifierLengthPtr] - 1) != index) {
super.classInstanceCreation(hasClassBody, isShort);
return;
}
QualifiedAllocationExpression alloc;
// astPtr--;
// astLengthPtr--;
alloc = new SelectionOnQualifiedAllocationExpression();
alloc.sourceEnd = endPosition; //the position has been stored explicitly
if (!isShort)
{
int length;
if ((length = expressionLengthStack[expressionLengthPtr--]) != 0) {
expressionPtr -= length;
System.arraycopy(
expressionStack,
expressionPtr + 1,
alloc.arguments = new Expression[length],
0,
length);
}
}
else
alloc.arguments=new Expression[0];
// trick to avoid creating a selection on type reference
char [] oldIdent = this.assistIdentifier();
this.setAssistIdentifier(null);
// alloc.type = getTypeReference(0);
alloc.member = this.expressionStack[this.expressionPtr--];
this.expressionLengthPtr--;
this.setAssistIdentifier(oldIdent);
//the default constructor with the correct number of argument
//will be created and added by the TC (see createsInternalConstructorWithBinding)
alloc.sourceStart = intStack[intPtr--];
pushOnExpressionStack(alloc);
this.assistNode = alloc;
this.lastCheckPoint = alloc.sourceEnd + 1;
if (!diet){
this.restartRecovery = AssistParser.STOP_AT_CURSOR; // force to restart in recovery mode
this.lastIgnoredToken = -1;
}
this.isOrphanCompletionNode = true;
// } else {
// super.classInstanceCreation(hasClassBody, isShort);
// }
}
protected void consumeEnterVariable() {
// EnterVariable ::= $empty
// do nothing by default
super.consumeEnterVariable();
// AbstractVariableDeclaration variable = (AbstractVariableDeclaration) astStack[astPtr];
// if (variable.type == assistNode){
// if (!diet){
// this.restartRecovery = true; // force to restart in recovery mode
// this.lastIgnoredToken = -1;
// }
// isOrphanCompletionNode = false; // already attached inside variable decl
// }
}
protected void consumeExitVariableWithInitialization() {
super.consumeExitVariableWithInitialization();
// does not keep the initialization if selection is not inside
AbstractVariableDeclaration variable = (AbstractVariableDeclaration) astStack[astPtr];
int start = variable.initialization.sourceStart;
int end = variable.initialization.sourceEnd;
if ((selectionStart < start) && (selectionEnd < start) ||
(selectionStart > end) && (selectionEnd > end)) {
if (STOP_AT_CURSOR)
variable.initialization = null;
}
}
protected void consumeCallExpressionWithSimpleName() {
if (this.indexOfAssistIdentifier() < 0) {
super.consumeCallExpressionWithSimpleName();
return;
}
FieldReference fieldReference =
new SelectionOnFieldReference(
identifierStack[identifierPtr],
identifierPositionStack[identifierPtr--]);
identifierLengthPtr--;
if ((fieldReference.receiver = expressionStack[expressionPtr]).isThis()) { //fieldReferenceerence begins at the this
fieldReference.sourceStart = fieldReference.receiver.sourceStart;
}
expressionStack[expressionPtr] = fieldReference;
assistNode = fieldReference;
this.lastCheckPoint = fieldReference.sourceEnd + 1;
if (!diet){
this.restartRecovery = AssistParser.STOP_AT_CURSOR; // force to restart in recovery mode
this.lastIgnoredToken = -1;
}
this.isOrphanCompletionNode = true;
}
protected void consumeMemberExpressionWithSimpleName() {
if (this.indexOfAssistIdentifier() < 0) {
super.consumeMemberExpressionWithSimpleName();
return;
}
FieldReference fieldReference =
new SelectionOnFieldReference(
identifierStack[identifierPtr],
identifierPositionStack[identifierPtr--]);
identifierLengthPtr--;
if ((fieldReference.receiver = expressionStack[expressionPtr]).isThis()) { //fieldReferenceerence begins at the this
fieldReference.sourceStart = fieldReference.receiver.sourceStart;
}
expressionStack[expressionPtr] = fieldReference;
assistNode = fieldReference;
this.lastCheckPoint = fieldReference.sourceEnd + 1;
if (!diet){
this.restartRecovery = AssistParser.STOP_AT_CURSOR; // force to restart in recovery mode
this.lastIgnoredToken = -1;
}
this.isOrphanCompletionNode = true;
}
protected void consumeFormalParameter(boolean isVarArgs) {
if (this.indexOfAssistIdentifier() < 0) {
super.consumeFormalParameter(isVarArgs);
// if((!diet || dietInt != 0) && astPtr > -1) {
// Argument argument = (Argument) astStack[astPtr];
// if(argument.type == assistNode) {
// isOrphanCompletionNode = true;
// this.restartRecovery = true; // force to restart in recovery mode
// this.lastIgnoredToken = -1;
// }
// }
} else {
identifierLengthPtr--;
char[] identifierName = identifierStack[identifierPtr];
long namePositions = identifierPositionStack[identifierPtr--];
// int extendedDimensions = this.intStack[this.intPtr--];
// int endOfEllipsis = 0;
// if (isVarArgs) {
// endOfEllipsis = this.intStack[this.intPtr--];
// }
// int firstDimensions = this.intStack[this.intPtr--];
// final int typeDimensions = firstDimensions + extendedDimensions;
// TypeReference type = getTypeReference(typeDimensions);
// if (isVarArgs) {
// type = copyDims(type, typeDimensions + 1);
// if (extendedDimensions == 0) {
// type.sourceEnd = endOfEllipsis;
// }
// type.bits |= ASTNode.IsVarArgs; // set isVarArgs
// }
// int modifierPositions = intStack[intPtr--];
// intPtr--;
int modifierPositions=(int) (namePositions >>> 32);
Argument arg =
new SelectionOnArgumentName(
identifierName,
namePositions,
null,
ClassFileConstants.AccDefault);
// intStack[intPtr + 1] & ~ClassFileConstants.AccDeprecated); // modifiers
arg.declarationSourceStart = modifierPositions;
pushOnAstStack(arg);
assistNode = arg;
this.lastCheckPoint = (int) namePositions;
isOrphanCompletionNode = true;
if (!diet){
this.restartRecovery = AssistParser.STOP_AT_CURSOR; // force to restart in recovery mode
this.lastIgnoredToken = -1;
}
/* if incomplete method header, listLength counter will not have been reset,
indicating that some arguments are available on the stack */
listLength++;
}
}
protected void consumeLocalVariableDeclarationStatement() {
super.consumeLocalVariableDeclarationStatement();
// force to restart in recovery mode if the declaration contains the selection
if (!this.diet) {
LocalDeclaration localDeclaration = (LocalDeclaration) this.astStack[this.astPtr];
if ((this.selectionStart >= localDeclaration.sourceStart)
&& (this.selectionEnd <= localDeclaration.sourceEnd)) {
this.restartRecovery = AssistParser.STOP_AT_CURSOR;
this.lastIgnoredToken = -1;
}
}
}
// Nothing here applicable to javascript
//protected void consumeMethodInvocationPrimary() {
// //optimize the push/pop
// //FunctionInvocation ::= Primary '.' 'Identifier' '(' ArgumentListopt ')'
//
// char[] selector = identifierStack[identifierPtr];
// int accessMode;
// if(selector == this.assistIdentifier()) {
// if(CharOperation.equals(selector, SUPER)) {
// accessMode = ExplicitConstructorCall.Super;
// } else if(CharOperation.equals(selector, THIS)) {
// accessMode = ExplicitConstructorCall.This;
// } else {
// super.consumeMethodInvocationPrimary();
// return;
// }
// } else {
// super.consumeMethodInvocationPrimary();
// return;
// }
//
// final ExplicitConstructorCall constructorCall = new SelectionOnExplicitConstructorCall(accessMode);
// constructorCall.sourceEnd = rParenPos;
// int length;
// if ((length = expressionLengthStack[expressionLengthPtr--]) != 0) {
// expressionPtr -= length;
// System.arraycopy(expressionStack, expressionPtr + 1, constructorCall.arguments = new Expression[length], 0, length);
// }
// constructorCall.qualification = expressionStack[expressionPtr--];
// constructorCall.sourceStart = constructorCall.qualification.sourceStart;
//
// if (!diet){
// pushOnAstStack(constructorCall);
// this.restartRecovery = AssistParser.STOP_AT_CURSOR; // force to restart in recovery mode
// this.lastIgnoredToken = -1;
// } else {
// pushOnExpressionStack(new Expression(){
// public TypeBinding resolveType(BlockScope scope) {
// constructorCall.resolve(scope);
// return null;
// }
// public StringBuffer printExpression(int indent, StringBuffer output) {
// return output;
// }
// });
// }
//
// this.assistNode = constructorCall;
// this.lastCheckPoint = constructorCall.sourceEnd + 1;
// this.isOrphanCompletionNode = true;
//}
protected void consumeToken(int token) {
super.consumeToken(token);
// if in a method or if in a field initializer
if (isInsideMethod() || isInsideFieldInitialization()) {
switch (token) {
case TokenNamecase :
pushOnElementStack(K_BETWEEN_CASE_AND_COLON);
break;
case TokenNameCOLON:
if(topKnownElementKind(SELECTION_OR_ASSIST_PARSER) == K_BETWEEN_CASE_AND_COLON) {
popElement(K_BETWEEN_CASE_AND_COLON);
}
break;
}
}
}
public ImportReference createAssistImportReference(char[][] tokens, long[] positions){
return new SelectionOnImportReference(tokens, positions);
}
protected JavadocParser createJavadocParser() {
return new SelectionJavadocParser(this);
}
protected LocalDeclaration createLocalDeclaration(char[] assistName,int sourceStart,int sourceEnd) {
if (this.indexOfAssistIdentifier() < 0) {
return super.createLocalDeclaration(assistName, sourceStart, sourceEnd);
} else {
SelectionOnLocalName local = new SelectionOnLocalName(assistName, sourceStart, sourceEnd);
this.assistNode = local;
this.lastCheckPoint = sourceEnd + 1;
return local;
}
}
public NameReference createQualifiedAssistNameReference(char[][] previousIdentifiers, char[] assistName, long[] positions){
return new SelectionOnQualifiedNameReference(
previousIdentifiers,
assistName,
positions);
}
public TypeReference createQualifiedAssistTypeReference(char[][] previousIdentifiers, char[] assistName, long[] positions){
return new SelectionOnQualifiedTypeReference(
previousIdentifiers,
assistName,
positions);
}
public NameReference createSingleAssistNameReference(char[] assistName, long position) {
return new SelectionOnSingleNameReference(assistName, position);
}
public TypeReference createSingleAssistTypeReference(char[] assistName, long position) {
return new SelectionOnSingleTypeReference(assistName, position);
}
public CompilationUnitDeclaration dietParse(ICompilationUnit sourceUnit, CompilationResult compilationResult, int start, int end) {
this.selectionStart = start;
this.selectionEnd = end;
SelectionScanner selectionScanner = (SelectionScanner)this.scanner;
selectionScanner.selectionIdentifier = null;
selectionScanner.selectionStart = start;
selectionScanner.selectionEnd = end;
return this.dietParse(sourceUnit, compilationResult);
}
protected NameReference getUnspecifiedReference() {
/* build a (unspecified) NameReference which may be qualified*/
int completionIndex;
/* no need to take action if not inside completed identifiers */
if ((completionIndex = indexOfAssistIdentifier()) < 0) {
return super.getUnspecifiedReference();
}
int length = identifierLengthStack[identifierLengthPtr];
NameReference nameReference;
/* retrieve identifiers subset and whole positions, the completion node positions
should include the entire replaced source. */
char[][] subset = identifierSubSet(completionIndex);
identifierLengthPtr--;
identifierPtr -= length;
long[] positions = new long[length];
System.arraycopy(
identifierPositionStack,
identifierPtr + 1,
positions,
0,
length);
/* build specific completion on name reference */
if (completionIndex == 0) {
/* completion inside first identifier */
nameReference = this.createSingleAssistNameReference(assistIdentifier(), positions[0]);
} else {
/* completion inside subsequent identifier */
nameReference = this.createQualifiedAssistNameReference(subset, assistIdentifier(), positions);
}
assistNode = nameReference;
this.lastCheckPoint = nameReference.sourceEnd + 1;
if (!diet){
this.restartRecovery = true; // force to restart in recovery mode
this.lastIgnoredToken = -1;
}
this.isOrphanCompletionNode = true;
return nameReference;
}
/*
* Copy of code from superclass with the following change:
* In the case of qualified name reference if the cursor location is on the
* qualified name reference, then create a CompletionOnQualifiedNameReference
* instead.
*/
protected NameReference getUnspecifiedReferenceOptimized() {
int index = indexOfAssistIdentifier();
NameReference reference = super.getUnspecifiedReferenceOptimized();
if (index >= 0){
if (!diet){
this.restartRecovery = AssistParser.STOP_AT_CURSOR; // force to restart in recovery mode
this.lastIgnoredToken = -1;
}
this.isOrphanCompletionNode = true;
}
return reference;
}
public void initializeScanner(){
this.scanner = new SelectionScanner(this.options.sourceLevel);
}
protected MessageSend newMessageSend() {
// '(' ArgumentListopt ')'
// the arguments are on the expression stack
int numArgs=expressionLengthStack[expressionLengthPtr];
Expression receiver = expressionStack[expressionPtr-numArgs];
// char[] selector = identifierStack[identifierPtr];
// if (selector != this.assistIdentifier()){
if (!(receiver instanceof SelectionOnSingleNameReference || receiver instanceof SelectionOnFieldReference))
{
return super.newMessageSend();
}
MessageSend messageSend = new SelectionOnMessageSend();
int length;
if ((length = expressionLengthStack[expressionLengthPtr--]) != 0) {
expressionPtr -= length;
System.arraycopy(
expressionStack,
expressionPtr + 1,
messageSend.arguments = new Expression[length],
0,
length);
}
assistNode = messageSend;
if (!diet){
this.restartRecovery = AssistParser.STOP_AT_CURSOR; // force to restart in recovery mode
this.lastIgnoredToken = -1;
}
this.isOrphanCompletionNode = true;
return messageSend;
}
public CompilationUnitDeclaration parse(ICompilationUnit sourceUnit, CompilationResult compilationResult, int start, int end) {
if (end == -1) return super.parse(sourceUnit, compilationResult, start, end);
this.selectionStart = start;
this.selectionEnd = end;
SelectionScanner selectionScanner = (SelectionScanner)this.scanner;
selectionScanner.selectionIdentifier = null;
selectionScanner.selectionStart = start;
selectionScanner.selectionEnd = end;
return super.parse(sourceUnit, compilationResult, -1, -1/*parse without reseting the scanner*/);
}
/*
* Reset context so as to resume to regular parse loop
* If unable to reset for resuming, answers false.
*
* Move checkpoint location, reset internal stacks and
* decide which grammar goal is activated.
*/
protected boolean resumeAfterRecovery() {
/* if reached assist node inside method body, but still inside nested type,
should continue in diet mode until the end of the method body */
if (this.assistNode != null
&& !(referenceContext instanceof CompilationUnitDeclaration)){
currentElement.preserveEnclosingBlocks();
if (currentElement.enclosingType() == null) {
if(!(currentElement instanceof RecoveredType)) {
this.resetStacks();
return false;
}
RecoveredType recoveredType = (RecoveredType)currentElement;
if(recoveredType.typeDeclaration != null && recoveredType.typeDeclaration.allocation == this.assistNode){
this.resetStacks();
return false;
}
}
}
return super.resumeAfterRecovery();
}
public void selectionIdentifierCheck(){
if (checkRecoveredType()) return;
}
public void setAssistIdentifier(char[] assistIdent){
((SelectionScanner)scanner).selectionIdentifier = assistIdent;
}
/*
* Update recovery state based on current parser/scanner state
*/
protected void updateRecoveryState() {
/* expose parser state to recovery state */
currentElement.updateFromParserState();
/* may be able to retrieve completionNode as an orphan, and then attach it */
this.selectionIdentifierCheck();
this.attachOrphanCompletionNode();
// if an assist node has been found and a recovered element exists,
// mark enclosing blocks as to be preserved
if (this.assistNode != null && this.currentElement != null) {
currentElement.preserveEnclosingBlocks();
}
/* check and update recovered state based on current token,
this action is also performed when shifting token after recovery
got activated once.
*/
this.recoveryTokenCheck();
}
public String toString() {
String s = Util.EMPTY_STRING;
s = s + "elementKindStack : int[] = {"; //$NON-NLS-1$
for (int i = 0; i <= elementPtr; i++) {
s = s + String.valueOf(elementKindStack[i]) + ","; //$NON-NLS-1$
}
s = s + "}\n"; //$NON-NLS-1$
s = s + "elementInfoStack : int[] = {"; //$NON-NLS-1$
for (int i = 0; i <= elementPtr; i++) {
s = s + String.valueOf(elementInfoStack[i]) + ","; //$NON-NLS-1$
}
s = s + "}\n"; //$NON-NLS-1$
return s + super.toString();
}
public int getCursorLocation() {
return this.selectionStart;
}
public void createAssistTypeForAllocation(AllocationExpression expression) {
}
}