1 /*******************************************************************************
2 * Copyright (c) 2000, 2001, 2002 International Business Machines Corp. and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v0.5
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v05.html
9 * IBM Corporation - initial API and implementation
10 ******************************************************************************/
11 package net.sourceforge.phpdt.internal.codeassist.select;
14 * Parser able to build specific completion parse nodes, given a cursorLocation.
16 * Cursor location denotes the position of the last character behind which completion
18 * -1 means completion at the very beginning of the source
19 * 0 means completion behind the first character
20 * n means completion behind the n-th character
23 import net.sourceforge.phpdt.internal.compiler.*;
24 import net.sourceforge.phpdt.internal.compiler.env.*;
26 import net.sourceforge.phpdt.internal.codeassist.impl.*;
27 import net.sourceforge.phpdt.internal.compiler.ast.*;
28 import net.sourceforge.phpdt.internal.compiler.lookup.BlockScope;
29 import net.sourceforge.phpdt.internal.compiler.lookup.TypeBinding;
30 import net.sourceforge.phpdt.internal.compiler.parser.*;
31 import net.sourceforge.phpdt.internal.compiler.problem.*;
32 import net.sourceforge.phpdt.internal.compiler.util.CharOperation;
34 public class SelectionParser extends AssistParser {
38 public int selectionStart, selectionEnd;
40 public static final char[] SUPER = "super".toCharArray(); //$NON-NLS-1$
41 public static final char[] THIS = "this".toCharArray(); //$NON-NLS-1$
43 public SelectionParser(ProblemReporter problemReporter, boolean assertMode) {
44 super(problemReporter, assertMode);
46 public char[] assistIdentifier(){
47 return ((SelectionScanner)scanner).selectionIdentifier;
49 protected void attachOrphanCompletionNode(){
50 if (isOrphanCompletionNode){
51 AstNode orphan = this.assistNode;
52 isOrphanCompletionNode = false;
55 /* if in context of a type, then persists the identifier into a fake field return type */
56 if (currentElement instanceof RecoveredType){
57 RecoveredType recoveredType = (RecoveredType)currentElement;
58 /* filter out cases where scanner is still inside type header */
59 if (recoveredType.foundOpeningBrace) {
60 /* generate a pseudo field with a completion on type reference */
61 if (orphan instanceof TypeReference){
62 currentElement = currentElement.add(new SelectionOnFieldType((TypeReference)orphan), 0);
68 Statement statement = (Statement)wrapWithExplicitConstructorCallIfNeeded(orphan);
69 currentElement = currentElement.add(statement, 0);
70 currentToken = 0; // given we are not on an eof, we do not want side effects caused by looked-ahead token
74 private boolean checkRecoveredType() {
75 if (currentElement instanceof RecoveredType){
76 /* check if current awaiting identifier is the completion identifier */
77 if (this.indexOfAssistIdentifier() < 0) return false;
79 if ((lastErrorEndPosition >= selectionStart)
80 && (lastErrorEndPosition <= selectionEnd+1)){
83 RecoveredType recoveredType = (RecoveredType)currentElement;
84 /* filter out cases where scanner is still inside type header */
85 if (recoveredType.foundOpeningBrace) {
86 this.assistNode = this.getTypeReference(0);
87 this.lastCheckPoint = this.assistNode.sourceEnd + 1;
88 this.isOrphanCompletionNode = true;
94 protected void classInstanceCreation(boolean alwaysQualified) {
96 // ClassInstanceCreationExpression ::= 'new' ClassType '(' ArgumentListopt ')' ClassBodyopt
98 // ClassBodyopt produces a null item on the astStak if it produces NO class body
99 // An empty class body produces a 0 on the length stack.....
102 if (((length = astLengthStack[astLengthPtr]) == 1)
103 && (astStack[astPtr] == null)) {
105 if (this.indexOfAssistIdentifier() < 0) {
106 super.classInstanceCreation(alwaysQualified);
109 QualifiedAllocationExpression alloc;
112 alloc = new SelectionOnQualifiedAllocationExpression();
113 alloc.sourceEnd = endPosition; //the position has been stored explicitly
115 if ((length = expressionLengthStack[expressionLengthPtr--]) != 0) {
116 expressionPtr -= length;
120 alloc.arguments = new Expression[length],
124 // trick to avoid creating a selection on type reference
125 char [] oldIdent = this.assistIdentifier();
126 this.setAssistIdentifier(null);
127 alloc.type = getTypeReference(0);
128 this.setAssistIdentifier(oldIdent);
130 //the default constructor with the correct number of argument
131 //will be created and added by the TC (see createsInternalConstructorWithBinding)
132 alloc.sourceStart = intStack[intPtr--];
133 pushOnExpressionStack(alloc);
135 this.assistNode = alloc;
136 this.lastCheckPoint = alloc.sourceEnd + 1;
138 this.restartRecovery = true; // force to restart in recovery mode
139 this.lastIgnoredToken = -1;
141 this.isOrphanCompletionNode = true;
143 super.classInstanceCreation(alwaysQualified);
147 protected void consumeArrayCreationExpression() {
148 // ArrayCreationExpression ::= 'new' PrimitiveType DimWithOrWithOutExprs ArrayInitializeropt
149 // ArrayCreationExpression ::= 'new' ClassOrInterfaceType DimWithOrWithOutExprs ArrayInitializeropt
151 super.consumeArrayCreationExpression();
153 ArrayAllocationExpression alloc = (ArrayAllocationExpression)expressionStack[expressionPtr];
154 if (alloc.type == assistNode){
156 this.restartRecovery = true; // force to restart in recovery mode
157 this.lastIgnoredToken = -1;
159 this.isOrphanCompletionNode = true;
162 protected void consumeEnterAnonymousClassBody() {
163 // EnterAnonymousClassBody ::= $empty
165 if (this.indexOfAssistIdentifier() < 0) {
166 super.consumeEnterAnonymousClassBody();
169 QualifiedAllocationExpression alloc;
170 AnonymousLocalTypeDeclaration anonymousType =
171 new AnonymousLocalTypeDeclaration(this.compilationUnit.compilationResult);
173 anonymousType.allocation = new SelectionOnQualifiedAllocationExpression(anonymousType);
174 markCurrentMethodWithLocalType();
175 pushOnAstStack(anonymousType);
177 alloc.sourceEnd = rParenPos; //the position has been stored explicitly
179 if ((argumentLength = expressionLengthStack[expressionLengthPtr--]) != 0) {
180 expressionPtr -= argumentLength;
184 alloc.arguments = new Expression[argumentLength],
188 // trick to avoid creating a selection on type reference
189 char [] oldIdent = this.assistIdentifier();
190 this.setAssistIdentifier(null);
191 alloc.type = getTypeReference(0);
192 this.setAssistIdentifier(oldIdent);
194 anonymousType.sourceEnd = alloc.sourceEnd;
195 //position at the type while it impacts the anonymous declaration
196 anonymousType.sourceStart = anonymousType.declarationSourceStart = alloc.type.sourceStart;
197 alloc.sourceStart = intStack[intPtr--];
198 pushOnExpressionStack(alloc);
201 this.lastCheckPoint = alloc.sourceEnd + 1;
203 this.restartRecovery = true; // force to restart in recovery mode
204 this.lastIgnoredToken = -1;
206 this.isOrphanCompletionNode = true;
208 anonymousType.bodyStart = scanner.currentPosition;
209 listLength = 0; // will be updated when reading super-interfaces
211 if (currentElement != null){
212 lastCheckPoint = anonymousType.bodyStart;
213 currentElement = currentElement.add(anonymousType, 0); // the recoveryTokenCheck will deal with the open brace
214 lastIgnoredToken = -1;
217 protected void consumeEnterVariable() {
218 // EnterVariable ::= $empty
219 // do nothing by default
221 super.consumeEnterVariable();
223 AbstractVariableDeclaration variable = (AbstractVariableDeclaration) astStack[astPtr];
224 if (variable.type == assistNode){
226 this.restartRecovery = true; // force to restart in recovery mode
227 this.lastIgnoredToken = -1;
229 isOrphanCompletionNode = false; // already attached inside variable decl
233 protected void consumeExitVariableWithInitialization() {
234 super.consumeExitVariableWithInitialization();
236 // does not keep the initialization if selection is not inside
237 AbstractVariableDeclaration variable = (AbstractVariableDeclaration) astStack[astPtr];
238 int start = variable.initialization.sourceStart;
239 int end = variable.initialization.sourceEnd;
240 if ((selectionStart < start) && (selectionEnd < start) ||
241 (selectionStart > end) && (selectionEnd > end)) {
242 variable.initialization = null;
246 protected void consumeFieldAccess(boolean isSuperAccess) {
247 // FieldAccess ::= Primary '.' 'Identifier'
248 // FieldAccess ::= 'super' '.' 'Identifier'
250 if (this.indexOfAssistIdentifier() < 0) {
251 super.consumeFieldAccess(isSuperAccess);
254 FieldReference fieldReference =
255 new SelectionOnFieldReference(
256 identifierStack[identifierPtr],
257 identifierPositionStack[identifierPtr--]);
258 identifierLengthPtr--;
259 if (isSuperAccess) { //considerates the fieldReferenceerence beginning at the 'super' ....
260 fieldReference.sourceStart = intStack[intPtr--];
261 fieldReference.receiver = new SuperReference(fieldReference.sourceStart, endPosition);
262 pushOnExpressionStack(fieldReference);
263 } else { //optimize push/pop
264 if ((fieldReference.receiver = expressionStack[expressionPtr]).isThis()) { //fieldReferenceerence begins at the this
265 fieldReference.sourceStart = fieldReference.receiver.sourceStart;
267 expressionStack[expressionPtr] = fieldReference;
269 assistNode = fieldReference;
270 this.lastCheckPoint = fieldReference.sourceEnd + 1;
272 this.restartRecovery = true; // force to restart in recovery mode
273 this.lastIgnoredToken = -1;
275 this.isOrphanCompletionNode = true;
277 protected void consumeFormalParameter() {
278 if (this.indexOfAssistIdentifier() < 0) {
279 super.consumeFormalParameter();
282 identifierLengthPtr--;
283 char[] name = identifierStack[identifierPtr];
284 long namePositions = identifierPositionStack[identifierPtr--];
285 TypeReference type = getTypeReference(intStack[intPtr--] + intStack[intPtr--]);
288 new SelectionOnArgumentName(
292 intStack[intPtr + 1] & ~AccDeprecated); // modifiers
296 this.lastCheckPoint = (int) namePositions;
297 isOrphanCompletionNode = true;
300 this.restartRecovery = true; // force to restart in recovery mode
301 this.lastIgnoredToken = -1;
304 /* if incomplete method header, listLength counter will not have been reset,
305 indicating that some arguments are available on the stack */
309 protected void consumeInstanceOfExpression(int op) {
310 if (indexOfAssistIdentifier() < 0) {
311 super.consumeInstanceOfExpression(op);
313 getTypeReference(intStack[intPtr--]);
314 this.isOrphanCompletionNode = true;
315 this.restartRecovery = true;
316 this.lastIgnoredToken = -1;
319 protected void consumeMethodInvocationName() {
320 // MethodInvocation ::= Name '(' ArgumentListopt ')'
322 // when the name is only an identifier...we have a message send to "this" (implicit)
324 char[] selector = identifierStack[identifierPtr];
326 if(selector == this.assistIdentifier()) {
327 if(CharOperation.equals(selector, SUPER)) {
328 accessMode = ExplicitConstructorCall.Super;
329 } else if(CharOperation.equals(selector, THIS)) {
330 accessMode = ExplicitConstructorCall.This;
332 super.consumeMethodInvocationName();
336 super.consumeMethodInvocationName();
340 final ExplicitConstructorCall constructorCall = new SelectionOnExplicitConstructorCall(accessMode);
341 constructorCall.sourceEnd = rParenPos;
342 constructorCall.sourceStart = (int) (identifierPositionStack[identifierPtr] >>> 32);
344 if ((length = expressionLengthStack[expressionLengthPtr--]) != 0) {
345 expressionPtr -= length;
346 System.arraycopy(expressionStack, expressionPtr + 1, constructorCall.arguments = new Expression[length], 0, length);
350 pushOnAstStack(constructorCall);
351 this.restartRecovery = true; // force to restart in recovery mode
352 this.lastIgnoredToken = -1;
354 pushOnExpressionStack(new Expression(){
355 public TypeBinding resolveType(BlockScope scope) {
356 constructorCall.resolve(scope);
361 this.assistNode = constructorCall;
362 this.lastCheckPoint = constructorCall.sourceEnd + 1;
363 this.isOrphanCompletionNode = true;
365 protected void consumeMethodInvocationPrimary() {
366 //optimize the push/pop
367 //MethodInvocation ::= Primary '.' 'Identifier' '(' ArgumentListopt ')'
369 char[] selector = identifierStack[identifierPtr];
371 if(selector == this.assistIdentifier()) {
372 if(CharOperation.equals(selector, SUPER)) {
373 accessMode = ExplicitConstructorCall.Super;
374 } else if(CharOperation.equals(selector, THIS)) {
375 accessMode = ExplicitConstructorCall.This;
377 super.consumeMethodInvocationPrimary();
381 super.consumeMethodInvocationPrimary();
385 final ExplicitConstructorCall constructorCall = new SelectionOnExplicitConstructorCall(accessMode);
386 constructorCall.sourceEnd = rParenPos;
388 if ((length = expressionLengthStack[expressionLengthPtr--]) != 0) {
389 expressionPtr -= length;
390 System.arraycopy(expressionStack, expressionPtr + 1, constructorCall.arguments = new Expression[length], 0, length);
392 constructorCall.qualification = expressionStack[expressionPtr--];
393 constructorCall.sourceStart = constructorCall.qualification.sourceStart;
396 pushOnAstStack(constructorCall);
397 this.restartRecovery = true; // force to restart in recovery mode
398 this.lastIgnoredToken = -1;
400 pushOnExpressionStack(new Expression(){
401 public TypeBinding resolveType(BlockScope scope) {
402 constructorCall.resolve(scope);
408 this.assistNode = constructorCall;
409 this.lastCheckPoint = constructorCall.sourceEnd + 1;
410 this.isOrphanCompletionNode = true;
412 protected void consumeTypeImportOnDemandDeclarationName() {
413 // TypeImportOnDemandDeclarationName ::= 'import' Name '.' '*'
414 /* push an ImportRef build from the last name
415 stored in the identifier stack. */
419 /* no need to take action if not inside assist identifiers */
420 if ((index = indexOfAssistIdentifier()) < 0) {
421 super.consumeTypeImportOnDemandDeclarationName();
424 /* retrieve identifiers subset and whole positions, the assist node positions
425 should include the entire replaced source. */
426 int length = identifierLengthStack[identifierLengthPtr];
427 char[][] subset = identifierSubSet(index+1); // include the assistIdentifier
428 identifierLengthPtr--;
429 identifierPtr -= length;
430 long[] positions = new long[length];
432 identifierPositionStack,
438 /* build specific assist node on import statement */
439 ImportReference reference = this.createAssistImportReference(subset, positions);
440 reference.onDemand = true;
441 assistNode = reference;
442 this.lastCheckPoint = reference.sourceEnd + 1;
444 pushOnAstStack(reference);
446 if (currentToken == TokenNameSEMICOLON){
447 reference.declarationSourceEnd = scanner.currentPosition - 1;
449 reference.declarationSourceEnd = (int) positions[length-1];
451 //endPosition is just before the ;
452 reference.declarationSourceStart = intStack[intPtr--];
453 // flush annotations defined prior to import statements
454 reference.declarationSourceEnd = this.flushAnnotationsDefinedPriorTo(reference.declarationSourceEnd);
457 if (currentElement != null){
458 lastCheckPoint = reference.declarationSourceEnd+1;
459 currentElement = currentElement.add(reference, 0);
460 lastIgnoredToken = -1;
461 restartRecovery = true; // used to avoid branching back into the regular automaton
464 public ImportReference createAssistImportReference(char[][] tokens, long[] positions){
465 return new SelectionOnImportReference(tokens, positions);
467 public ImportReference createAssistPackageReference(char[][] tokens, long[] positions){
468 return new SelectionOnPackageReference(tokens, positions);
470 protected LocalDeclaration createLocalDeclaration(Expression initialization,char[] name,int sourceStart,int sourceEnd) {
471 if (this.indexOfAssistIdentifier() < 0) {
472 return super.createLocalDeclaration(initialization, name, sourceStart, sourceEnd);
474 SelectionOnLocalName local = new SelectionOnLocalName(initialization, name, sourceStart, sourceEnd);
475 this.assistNode = local;
476 this.lastCheckPoint = sourceEnd + 1;
478 this.restartRecovery = true; // force to restart in recovery mode
479 this.lastIgnoredToken = -1;
484 public NameReference createQualifiedAssistNameReference(char[][] previousIdentifiers, char[] name, long[] positions){
485 return new SelectionOnQualifiedNameReference(
490 public TypeReference createQualifiedAssistTypeReference(char[][] previousIdentifiers, char[] name, long[] positions){
491 return new SelectionOnQualifiedTypeReference(
496 public NameReference createSingleAssistNameReference(char[] name, long position) {
497 return new SelectionOnSingleNameReference(name, position);
499 public TypeReference createSingleAssistTypeReference(char[] name, long position) {
500 return new SelectionOnSingleTypeReference(name, position);
502 public CompilationUnitDeclaration dietParse(ICompilationUnit sourceUnit, CompilationResult compilationResult, int selectionStart, int selectionEnd) {
504 this.selectionStart = selectionStart;
505 this.selectionEnd = selectionEnd;
506 SelectionScanner selectionScanner = (SelectionScanner)this.scanner;
507 selectionScanner.selectionIdentifier = null;
508 selectionScanner.selectionStart = selectionStart;
509 selectionScanner.selectionEnd = selectionEnd;
510 return this.dietParse(sourceUnit, compilationResult);
512 protected NameReference getUnspecifiedReference() {
513 /* build a (unspecified) NameReference which may be qualified*/
517 /* no need to take action if not inside completed identifiers */
518 if ((completionIndex = indexOfAssistIdentifier()) < 0) {
519 return super.getUnspecifiedReference();
522 int length = identifierLengthStack[identifierLengthPtr];
523 if (CharOperation.equals(assistIdentifier(), SUPER)){
525 if (completionIndex > 0){ // qualified super
526 // discard 'super' from identifier stacks
527 identifierLengthStack[identifierLengthPtr] = completionIndex;
528 int ptr = identifierPtr -= (length - completionIndex);
530 new SelectionOnQualifiedSuperReference(
532 (int)(identifierPositionStack[ptr+1] >>> 32),
533 (int) identifierPositionStack[ptr+1]);
534 } else { // standard super
535 identifierPtr -= length;
536 identifierLengthPtr--;
537 reference = new SelectionOnSuperReference((int)(identifierPositionStack[identifierPtr+1] >>> 32), (int) identifierPositionStack[identifierPtr+1]);
539 pushOnAstStack(reference);
540 this.assistNode = reference;
541 this.lastCheckPoint = reference.sourceEnd + 1;
542 if (!diet || dietInt != 0){
543 this.restartRecovery = true; // force to restart in recovery mode
544 this.lastIgnoredToken = -1;
546 this.isOrphanCompletionNode = true;
547 return new SingleNameReference(new char[0], 0); // dummy reference
549 NameReference nameReference;
550 /* retrieve identifiers subset and whole positions, the completion node positions
551 should include the entire replaced source. */
552 char[][] subset = identifierSubSet(completionIndex);
553 identifierLengthPtr--;
554 identifierPtr -= length;
555 long[] positions = new long[length];
557 identifierPositionStack,
562 /* build specific completion on name reference */
563 if (completionIndex == 0) {
564 /* completion inside first identifier */
565 nameReference = this.createSingleAssistNameReference(assistIdentifier(), positions[0]);
567 /* completion inside subsequent identifier */
568 nameReference = this.createQualifiedAssistNameReference(subset, assistIdentifier(), positions);
570 assistNode = nameReference;
571 this.lastCheckPoint = nameReference.sourceEnd + 1;
573 this.restartRecovery = true; // force to restart in recovery mode
574 this.lastIgnoredToken = -1;
576 this.isOrphanCompletionNode = true;
577 return nameReference;
580 * Copy of code from superclass with the following change:
581 * In the case of qualified name reference if the cursor location is on the
582 * qualified name reference, then create a CompletionOnQualifiedNameReference
585 protected NameReference getUnspecifiedReferenceOptimized() {
587 int index = indexOfAssistIdentifier();
588 NameReference reference = super.getUnspecifiedReferenceOptimized();
592 this.restartRecovery = true; // force to restart in recovery mode
593 this.lastIgnoredToken = -1;
595 this.isOrphanCompletionNode = true;
599 public void initializeScanner(){
600 this.scanner = new SelectionScanner(this.assertMode);
602 protected MessageSend newMessageSend() {
603 // '(' ArgumentListopt ')'
604 // the arguments are on the expression stack
606 char[] selector = identifierStack[identifierPtr];
607 if (selector != this.assistIdentifier()){
608 return super.newMessageSend();
610 MessageSend messageSend = new SelectionOnMessageSend();
612 if ((length = expressionLengthStack[expressionLengthPtr--]) != 0) {
613 expressionPtr -= length;
617 messageSend.arguments = new Expression[length],
621 assistNode = messageSend;
623 this.restartRecovery = true; // force to restart in recovery mode
624 this.lastIgnoredToken = -1;
627 this.isOrphanCompletionNode = true;
630 public CompilationUnitDeclaration parse(ICompilationUnit sourceUnit, CompilationResult compilationResult, int selectionStart, int selectionEnd) {
632 this.selectionStart = selectionStart;
633 this.selectionEnd = selectionEnd;
634 SelectionScanner selectionScanner = (SelectionScanner)this.scanner;
635 selectionScanner.selectionIdentifier = null;
636 selectionScanner.selectionStart = selectionStart;
637 selectionScanner.selectionEnd = selectionEnd;
638 return this.parse(sourceUnit, compilationResult);
641 * Reset context so as to resume to regular parse loop
642 * If unable to reset for resuming, answers false.
644 * Move checkpoint location, reset internal stacks and
645 * decide which grammar goal is activated.
647 protected boolean resumeAfterRecovery() {
649 /* if reached assist node inside method body, but still inside nested type,
650 should continue in diet mode until the end of the method body */
651 if (this.assistNode != null
652 && !(referenceContext instanceof CompilationUnitDeclaration)){
653 currentElement.preserveEnclosingBlocks();
654 if (currentElement.enclosingType() == null){
659 return super.resumeAfterRecovery();
662 public void selectionIdentifierCheck(){
663 if (checkRecoveredType()) return;
665 public void setAssistIdentifier(char[] assistIdent){
666 ((SelectionScanner)scanner).selectionIdentifier = assistIdent;
669 * Update recovery state based on current parser/scanner state
671 protected void updateRecoveryState() {
673 /* expose parser state to recovery state */
674 currentElement.updateFromParserState();
676 /* may be able to retrieve completionNode as an orphan, and then attach it */
677 this.selectionIdentifierCheck();
678 this.attachOrphanCompletionNode();
680 /* check and update recovered state based on current token,
681 this action is also performed when shifting token after recovery
684 this.recoveryTokenCheck();