/******************************************************************************* * 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) { } }