blob: cdca93064e4a5b39c89f6b5ecaca54c9d82db19e [file] [log] [blame]
package org.eclipse.jdt.internal.codeassist.complete;
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
/*
* Scanner aware of a cursor location so as to discard trailing portions of identifiers
* containing the cursor location.
*
* 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.jdt.internal.compiler.parser.*;
public class CompletionScanner extends Scanner {
public char[] completionIdentifier;
public int cursorLocation;
/* Source positions of the completedIdentifier
* if inside actual identifier, end goes to the actual identifier
* end, i.e. beyond cursor location
*/
public int completedIdentifierStart = 0;
public int completedIdentifierEnd = -1;
public static final char[] EmptyCompletionIdentifier = {};
public CompletionScanner(boolean assertMode) {
super(false, false, false, assertMode);
}
/*
* Truncate the current identifier if it is containing the cursor location. Since completion is performed
* on an identifier prefix.
*
*/
public char[] getCurrentIdentifierSource() {
if (completionIdentifier == null){
if (cursorLocation < startPosition && currentPosition == startPosition){ // fake empty identifier got issued
// remember actual identifier positions
completedIdentifierStart = startPosition;
completedIdentifierEnd = completedIdentifierStart - 1;
return completionIdentifier = EmptyCompletionIdentifier;
}
if (cursorLocation+1 >= startPosition && cursorLocation < currentPosition){
// remember actual identifier positions
completedIdentifierStart = startPosition;
completedIdentifierEnd = currentPosition - 1;
if (withoutUnicodePtr != 0){ // check unicode scenario
System.arraycopy(withoutUnicodeBuffer, 1, completionIdentifier = new char[withoutUnicodePtr], 0, withoutUnicodePtr);
} else {
int length = cursorLocation + 1 - startPosition;
// no char[] sharing around completionIdentifier, we want it to be unique so as to use identity checks
System.arraycopy(source, startPosition, (completionIdentifier = new char[length]), 0, length);
}
return completionIdentifier;
}
}
return super.getCurrentIdentifierSource();
}
/*
* Identifier splitting for unicodes.
* Only store the current unicode if we did not pass the cursorLocation.
* Note: this does not handle cases where the cursor is in the middle of a unicode
*/
public boolean getNextCharAsJavaIdentifierPart() {
int temp = currentPosition;
try {
if (((currentCharacter = source[currentPosition++]) == '\\')
&& (source[currentPosition] == 'u')) {
//-------------unicode traitement ------------
int c1, c2, c3, c4;
int unicodeSize = 6;
currentPosition++;
while (source[currentPosition] == 'u') {
currentPosition++;
unicodeSize++;
}
if (((c1 = Character.getNumericValue(source[currentPosition++])) > 15
|| c1 < 0)
|| ((c2 = Character.getNumericValue(source[currentPosition++])) > 15 || c2 < 0)
|| ((c3 = Character.getNumericValue(source[currentPosition++])) > 15 || c3 < 0)
|| ((c4 = Character.getNumericValue(source[currentPosition++])) > 15 || c4 < 0)) {
currentPosition = temp;
return false;
}
currentCharacter = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
if (!Character.isJavaIdentifierPart(currentCharacter)) {
currentPosition = temp;
return false;
}
//need the unicode buffer
if (withoutUnicodePtr == 0) {
//buffer all the entries that have been left aside....
withoutUnicodePtr = currentPosition - unicodeSize - startPosition;
System.arraycopy(
source,
startPosition,
withoutUnicodeBuffer,
1,
withoutUnicodePtr);
}
if (temp < cursorLocation && cursorLocation < currentPosition-1){
throw new InvalidCursorLocation(InvalidCursorLocation.NO_COMPLETION_INSIDE_UNICODE);
}
// store the current unicode, only if we did not pass the cursorLocation
// Note: this does not handle cases where the cursor is in the middle of a unicode
if ((completionIdentifier != null)
|| (startPosition <= cursorLocation+1 && cursorLocation >= currentPosition-1)){
withoutUnicodeBuffer[++withoutUnicodePtr] = currentCharacter;
}
return true;
} //-------------end unicode traitement--------------
else {
if (!Character.isJavaIdentifierPart(currentCharacter)) {
currentPosition = temp;
return false;
}
if (withoutUnicodePtr != 0){
// store the current unicode, only if we did not pass the cursorLocation
// Note: this does not handle cases where the cursor is in the middle of a unicode
if ((completionIdentifier != null)
|| (startPosition <= cursorLocation+1 && cursorLocation >= currentPosition-1)){
withoutUnicodeBuffer[++withoutUnicodePtr] = currentCharacter;
}
}
return true;
}
} catch (IndexOutOfBoundsException e) {
currentPosition = temp;
return false;
}
}
public int getNextToken() throws InvalidInputException {
this.wasAcr = false;
if (diet) {
jumpOverMethodBody();
diet = false;
return currentPosition > source.length ? TokenNameEOF : TokenNameRBRACE;
}
try {
while (true) { //loop for jumping over comments
withoutUnicodePtr = 0;
//start with a new token (even comment written with unicode )
// ---------Consume white space and handles startPosition---------
int whiteStart = currentPosition;
boolean isWhiteSpace, foundWhiteSpaces;
do {
startPosition = currentPosition;
if (((currentCharacter = source[currentPosition++]) == '\\')
&& (source[currentPosition] == 'u')) {
isWhiteSpace = jumpOverUnicodeWhiteSpace();
} else {
if (recordLineSeparator
&& ((currentCharacter == '\r') || (currentCharacter == '\n')))
pushLineSeparator();
isWhiteSpace =
(currentCharacter == ' ') || Character.isWhitespace(currentCharacter);
}
/* completion requesting strictly inside blanks */
if ((whiteStart != currentPosition)
//&& (previousToken == TokenNameDOT)
&& (completionIdentifier == null)
&& (whiteStart <= cursorLocation+1)
&& (cursorLocation < startPosition)
&& !Character.isJavaIdentifierStart(currentCharacter)){
currentPosition = startPosition; // for next token read
return TokenNameIdentifier;
}
} while (isWhiteSpace);
if (tokenizeWhiteSpace && (whiteStart != currentPosition - 1)) {
// reposition scanner in case we are interested by spaces as tokens
currentPosition--;
startPosition = whiteStart;
return TokenNameWHITESPACE;
}
//little trick to get out in the middle of a source comptuation
if (currentPosition > eofPosition){
/* might be completing at eof (e.g. behind a dot) */
if (completionIdentifier == null &&
startPosition == cursorLocation + 1){
currentPosition = startPosition; // for being detected as empty free identifier
return TokenNameIdentifier;
}
return TokenNameEOF;
}
// ---------Identify the next token-------------
switch (currentCharacter) {
case '(' :
return TokenNameLPAREN;
case ')' :
return TokenNameRPAREN;
case '{' :
return TokenNameLBRACE;
case '}' :
return TokenNameRBRACE;
case '[' :
return TokenNameLBRACKET;
case ']' :
return TokenNameRBRACKET;
case ';' :
return TokenNameSEMICOLON;
case ',' :
return TokenNameCOMMA;
case '.' :
if (getNextCharAsDigit())
return scanNumber(true);
return TokenNameDOT;
case '+' :
{
int test;
if ((test = getNextChar('+', '=')) == 0)
return TokenNamePLUS_PLUS;
if (test > 0)
return TokenNamePLUS_EQUAL;
return TokenNamePLUS;
}
case '-' :
{
int test;
if ((test = getNextChar('-', '=')) == 0)
return TokenNameMINUS_MINUS;
if (test > 0)
return TokenNameMINUS_EQUAL;
return TokenNameMINUS;
}
case '~' :
return TokenNameTWIDDLE;
case '!' :
if (getNextChar('='))
return TokenNameNOT_EQUAL;
return TokenNameNOT;
case '*' :
if (getNextChar('='))
return TokenNameMULTIPLY_EQUAL;
return TokenNameMULTIPLY;
case '%' :
if (getNextChar('='))
return TokenNameREMAINDER_EQUAL;
return TokenNameREMAINDER;
case '<' :
{
int test;
if ((test = getNextChar('=', '<')) == 0)
return TokenNameLESS_EQUAL;
if (test > 0) {
if (getNextChar('='))
return TokenNameLEFT_SHIFT_EQUAL;
return TokenNameLEFT_SHIFT;
}
return TokenNameLESS;
}
case '>' :
{
int test;
if ((test = getNextChar('=', '>')) == 0)
return TokenNameGREATER_EQUAL;
if (test > 0) {
if ((test = getNextChar('=', '>')) == 0)
return TokenNameRIGHT_SHIFT_EQUAL;
if (test > 0) {
if (getNextChar('='))
return TokenNameUNSIGNED_RIGHT_SHIFT_EQUAL;
return TokenNameUNSIGNED_RIGHT_SHIFT;
}
return TokenNameRIGHT_SHIFT;
}
return TokenNameGREATER;
}
case '=' :
if (getNextChar('='))
return TokenNameEQUAL_EQUAL;
return TokenNameEQUAL;
case '&' :
{
int test;
if ((test = getNextChar('&', '=')) == 0)
return TokenNameAND_AND;
if (test > 0)
return TokenNameAND_EQUAL;
return TokenNameAND;
}
case '|' :
{
int test;
if ((test = getNextChar('|', '=')) == 0)
return TokenNameOR_OR;
if (test > 0)
return TokenNameOR_EQUAL;
return TokenNameOR;
}
case '^' :
if (getNextChar('='))
return TokenNameXOR_EQUAL;
return TokenNameXOR;
case '?' :
return TokenNameQUESTION;
case ':' :
return TokenNameCOLON;
case '\'' :
{
int test;
if ((test = getNextChar('\n', '\r')) == 0) {
throw new InvalidInputException(INVALID_CHARACTER_CONSTANT);
}
if (test > 0) {
// relocate if finding another quote fairly close: thus unicode '/u000D' will be fully consumed
for (int lookAhead = 0; lookAhead < 3; lookAhead++) {
if (currentPosition + lookAhead == source.length)
break;
if (source[currentPosition + lookAhead] == '\n')
break;
if (source[currentPosition + lookAhead] == '\'') {
currentPosition += lookAhead + 1;
break;
}
}
throw new InvalidInputException(INVALID_CHARACTER_CONSTANT);
}
}
if (getNextChar('\'')) {
// relocate if finding another quote fairly close: thus unicode '/u000D' will be fully consumed
for (int lookAhead = 0; lookAhead < 3; lookAhead++) {
if (currentPosition + lookAhead == source.length)
break;
if (source[currentPosition + lookAhead] == '\n')
break;
if (source[currentPosition + lookAhead] == '\'') {
currentPosition += lookAhead + 1;
break;
}
}
throw new InvalidInputException(INVALID_CHARACTER_CONSTANT);
}
if (getNextChar('\\'))
scanEscapeCharacter();
else { // consume next character
unicodeAsBackSlash = false;
if (((currentCharacter = source[currentPosition++]) == '\\')
&& (source[currentPosition] == 'u')) {
getNextUnicodeChar();
} else {
if (withoutUnicodePtr != 0) {
withoutUnicodeBuffer[++withoutUnicodePtr] = currentCharacter;
}
}
}
if (getNextChar('\''))
return TokenNameCharacterLiteral;
// relocate if finding another quote fairly close: thus unicode '/u000D' will be fully consumed
for (int lookAhead = 0; lookAhead < 20; lookAhead++) {
if (currentPosition + lookAhead == source.length)
break;
if (source[currentPosition + lookAhead] == '\n')
break;
if (source[currentPosition + lookAhead] == '\'') {
currentPosition += lookAhead + 1;
break;
}
}
throw new InvalidInputException(INVALID_CHARACTER_CONSTANT);
case '"' :
try {
// consume next character
unicodeAsBackSlash = false;
if (((currentCharacter = source[currentPosition++]) == '\\')
&& (source[currentPosition] == 'u')) {
getNextUnicodeChar();
} else {
if (withoutUnicodePtr != 0) {
withoutUnicodeBuffer[++withoutUnicodePtr] = currentCharacter;
}
}
while (currentCharacter != '"') {
/**** \r and \n are not valid in string literals ****/
if ((currentCharacter == '\n') || (currentCharacter == '\r')) {
// relocate if finding another quote fairly close: thus unicode '/u000D' will be fully consumed
for (int lookAhead = 0; lookAhead < 50; lookAhead++) {
if (currentPosition + lookAhead == source.length)
break;
if (source[currentPosition + lookAhead] == '\n')
break;
if (source[currentPosition + lookAhead] == '\"') {
currentPosition += lookAhead + 1;
break;
}
}
throw new InvalidInputException(INVALID_CHAR_IN_STRING);
}
if (currentCharacter == '\\') {
int escapeSize = currentPosition;
boolean backSlashAsUnicodeInString = unicodeAsBackSlash;
//scanEscapeCharacter make a side effect on this value and we need the previous value few lines down this one
scanEscapeCharacter();
escapeSize = currentPosition - escapeSize;
if (withoutUnicodePtr == 0) {
//buffer all the entries that have been left aside....
withoutUnicodePtr = currentPosition - escapeSize - 1 - startPosition;
System.arraycopy(
source,
startPosition,
withoutUnicodeBuffer,
1,
withoutUnicodePtr);
withoutUnicodeBuffer[++withoutUnicodePtr] = currentCharacter;
} else { //overwrite the / in the buffer
withoutUnicodeBuffer[withoutUnicodePtr] = currentCharacter;
if (backSlashAsUnicodeInString) { //there are TWO \ in the stream where only one is correct
withoutUnicodePtr--;
}
}
}
// consume next character
unicodeAsBackSlash = false;
if (((currentCharacter = source[currentPosition++]) == '\\')
&& (source[currentPosition] == 'u')) {
getNextUnicodeChar();
} else {
if (withoutUnicodePtr != 0) {
withoutUnicodeBuffer[++withoutUnicodePtr] = currentCharacter;
}
}
}
} catch (IndexOutOfBoundsException e) {
throw new InvalidInputException(UNTERMINATED_STRING);
} catch (InvalidInputException e) {
if (e.getMessage().equals(INVALID_ESCAPE)) {
// relocate if finding another quote fairly close: thus unicode '/u000D' will be fully consumed
for (int lookAhead = 0; lookAhead < 50; lookAhead++) {
if (currentPosition + lookAhead == source.length)
break;
if (source[currentPosition + lookAhead] == '\n')
break;
if (source[currentPosition + lookAhead] == '\"') {
currentPosition += lookAhead + 1;
break;
}
}
}
throw e; // rethrow
}
if (startPosition <= cursorLocation && cursorLocation <= currentPosition-1){
throw new InvalidCursorLocation(InvalidCursorLocation.NO_COMPLETION_INSIDE_STRING);
}
return TokenNameStringLiteral;
case '/' :
{
int test;
if ((test = getNextChar('/', '*')) == 0) { //line comment
try { //get the next char
if (((currentCharacter = source[currentPosition++]) == '\\')
&& (source[currentPosition] == 'u')) {
//-------------unicode traitement ------------
int c1 = 0, c2 = 0, c3 = 0, c4 = 0;
currentPosition++;
while (source[currentPosition] == 'u') {
currentPosition++;
}
if ((c1 = Character.getNumericValue(source[currentPosition++])) > 15
|| c1 < 0
|| (c2 = Character.getNumericValue(source[currentPosition++])) > 15
|| c2 < 0
|| (c3 = Character.getNumericValue(source[currentPosition++])) > 15
|| c3 < 0
|| (c4 = Character.getNumericValue(source[currentPosition++])) > 15
|| c4 < 0) {
throw new InvalidInputException(INVALID_UNICODE_ESCAPE);
} else {
currentCharacter = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
}
}
//handle the \\u case manually into comment
if (currentCharacter == '\\') {
if (source[currentPosition] == '\\')
currentPosition++;
} //jump over the \\
while (currentCharacter != '\r' && currentCharacter != '\n') {
//get the next char
if (((currentCharacter = source[currentPosition++]) == '\\')
&& (source[currentPosition] == 'u')) {
//-------------unicode traitement ------------
int c1 = 0, c2 = 0, c3 = 0, c4 = 0;
currentPosition++;
while (source[currentPosition] == 'u') {
currentPosition++;
}
if ((c1 = Character.getNumericValue(source[currentPosition++])) > 15
|| c1 < 0
|| (c2 = Character.getNumericValue(source[currentPosition++])) > 15
|| c2 < 0
|| (c3 = Character.getNumericValue(source[currentPosition++])) > 15
|| c3 < 0
|| (c4 = Character.getNumericValue(source[currentPosition++])) > 15
|| c4 < 0) {
throw new InvalidInputException(INVALID_UNICODE_ESCAPE);
} else {
currentCharacter = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
}
}
//handle the \\u case manually into comment
if (currentCharacter == '\\') {
if (source[currentPosition] == '\\')
currentPosition++;
} //jump over the \\
}
recordComment(false);
if (startPosition <= cursorLocation && cursorLocation < currentPosition-1){
throw new InvalidCursorLocation(InvalidCursorLocation.NO_COMPLETION_INSIDE_COMMENT);
}
if (recordLineSeparator
&& ((currentCharacter == '\r') || (currentCharacter == '\n')))
pushLineSeparator();
if (tokenizeComments) {
currentPosition--; // reset one character behind
return TokenNameCOMMENT_LINE;
}
} catch (IndexOutOfBoundsException e) { //an eof will them be generated
if (tokenizeComments) {
currentPosition--; // reset one character behind
return TokenNameCOMMENT_LINE;
}
}
break;
}
if (test > 0) { //traditional and annotation comment
boolean isJavadoc = false, star = false;
// consume next character
unicodeAsBackSlash = false;
if (((currentCharacter = source[currentPosition++]) == '\\')
&& (source[currentPosition] == 'u')) {
getNextUnicodeChar();
} else {
if (withoutUnicodePtr != 0) {
withoutUnicodeBuffer[++withoutUnicodePtr] = currentCharacter;
}
}
if (currentCharacter == '*') {
isJavadoc = true;
star = true;
}
if (recordLineSeparator
&& ((currentCharacter == '\r') || (currentCharacter == '\n')))
pushLineSeparator();
try { //get the next char
if (((currentCharacter = source[currentPosition++]) == '\\')
&& (source[currentPosition] == 'u')) {
//-------------unicode traitement ------------
int c1 = 0, c2 = 0, c3 = 0, c4 = 0;
currentPosition++;
while (source[currentPosition] == 'u') {
currentPosition++;
}
if ((c1 = Character.getNumericValue(source[currentPosition++])) > 15
|| c1 < 0
|| (c2 = Character.getNumericValue(source[currentPosition++])) > 15
|| c2 < 0
|| (c3 = Character.getNumericValue(source[currentPosition++])) > 15
|| c3 < 0
|| (c4 = Character.getNumericValue(source[currentPosition++])) > 15
|| c4 < 0) {
throw new InvalidInputException(INVALID_UNICODE_ESCAPE);
} else {
currentCharacter = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
}
}
//handle the \\u case manually into comment
if (currentCharacter == '\\') {
if (source[currentPosition] == '\\')
currentPosition++;
} //jump over the \\
// empty comment is not a javadoc /**/
if (currentCharacter == '/') {
isJavadoc = false;
}
//loop until end of comment */
while ((currentCharacter != '/') || (!star)) {
if (recordLineSeparator
&& ((currentCharacter == '\r') || (currentCharacter == '\n')))
pushLineSeparator();
star = currentCharacter == '*';
//get next char
if (((currentCharacter = source[currentPosition++]) == '\\')
&& (source[currentPosition] == 'u')) {
//-------------unicode traitement ------------
int c1 = 0, c2 = 0, c3 = 0, c4 = 0;
currentPosition++;
while (source[currentPosition] == 'u') {
currentPosition++;
}
if ((c1 = Character.getNumericValue(source[currentPosition++])) > 15
|| c1 < 0
|| (c2 = Character.getNumericValue(source[currentPosition++])) > 15
|| c2 < 0
|| (c3 = Character.getNumericValue(source[currentPosition++])) > 15
|| c3 < 0
|| (c4 = Character.getNumericValue(source[currentPosition++])) > 15
|| c4 < 0) {
throw new InvalidInputException(INVALID_UNICODE_ESCAPE);
} else {
currentCharacter = (char) (((c1 * 16 + c2) * 16 + c3) * 16 + c4);
}
}
//handle the \\u case manually into comment
if (currentCharacter == '\\') {
if (source[currentPosition] == '\\')
currentPosition++;
} //jump over the \\
}
recordComment(isJavadoc);
if (startPosition <= cursorLocation && cursorLocation < currentPosition-1){
throw new InvalidCursorLocation(InvalidCursorLocation.NO_COMPLETION_INSIDE_COMMENT);
}
if (tokenizeComments) {
if (isJavadoc)
return TokenNameCOMMENT_JAVADOC;
return TokenNameCOMMENT_BLOCK;
}
} catch (IndexOutOfBoundsException e) {
throw new InvalidInputException(UNTERMINATED_COMMENT);
}
break;
}
if (getNextChar('='))
return TokenNameDIVIDE_EQUAL;
return TokenNameDIVIDE;
}
case '\u001a' :
if (atEnd())
return TokenNameEOF;
//the atEnd may not be <currentPosition == source.length> if source is only some part of a real (external) stream
throw new InvalidInputException("Ctrl-Z"); //$NON-NLS-1$
default :
if (Character.isJavaIdentifierStart(currentCharacter))
return scanIdentifierOrKeyword();
if (Character.isDigit(currentCharacter))
return scanNumber(false);
return TokenNameERROR;
}
}
} //-----------------end switch while try--------------------
catch (IndexOutOfBoundsException e) {
}
/* might be completing at very end of file (e.g. behind a dot) */
if (completionIdentifier == null &&
startPosition == cursorLocation + 1){
currentPosition = startPosition; // for being detected as empty free identifier
return TokenNameIdentifier;
}
return TokenNameEOF;
}
/*
* In case we actually read a keyword, but the cursor is located inside,
* we pretend we read an identifier.
*/
public int scanIdentifierOrKeyword() throws InvalidInputException {
int id = super.scanIdentifierOrKeyword();
// convert completed keyword into an identifier
if (id != TokenNameIdentifier
&& startPosition <= cursorLocation+1
&& cursorLocation < currentPosition){
return TokenNameIdentifier;
}
return id;
}
public int scanNumber(boolean dotPrefix) throws InvalidInputException {
int token = super.scanNumber(dotPrefix);
// consider completion just before a number to be ok, will insert before it
if (startPosition <= cursorLocation && cursorLocation < currentPosition){
throw new InvalidCursorLocation(InvalidCursorLocation.NO_COMPLETION_INSIDE_NUMBER);
}
return token;
}
}