package net.sourceforge.phpdt.internal.ui.text; /* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ import java.io.IOException; import net.sourceforge.phpdt.internal.corext.phpdoc.SingleCharReader; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; /** * Reads from a document either forwards or backwards. May be configured to skip * comments and strings. */ public class PHPCodeReader extends SingleCharReader { /** The EOF character */ public static final int EOF = -1; private boolean fSkipComments = false; private boolean fSkipStrings = false; private boolean fForward = false; private IDocument fDocument; private int fOffset; // The current text position within the editor's text private int fEnd = -1; private int fCachedLineNumber = -1; // Holds the last line we checked for single line comments public PHPCodeReader() { } /** * Returns the offset of the last read character. Should only be called * after read has been called. */ public int getOffset() { return fForward ? fOffset - 1 : fOffset; } public void configureForwardReader(IDocument document, int offset, int length, boolean skipComments, boolean skipStrings) throws IOException { fDocument = document; fOffset = offset; fSkipComments = skipComments; fSkipStrings = skipStrings; fForward = true; fEnd = Math.min(fDocument.getLength(), fOffset + length); } public void configureBackwardReader(IDocument document, int offset, boolean skipComments, boolean skipStrings) throws IOException { fDocument = document; fOffset = offset; fSkipComments = skipComments; fSkipStrings = skipStrings; fForward = false; try { fCachedLineNumber = fDocument.getLineOfOffset(fOffset); } catch (BadLocationException x) { throw new IOException(x.getMessage()); } } /* * @see Reader#close() */ public void close() throws IOException { fDocument = null; } /* * @see SingleCharReader#read() */ public int read() throws IOException { try { return fForward ? readForwards() : readBackwards(); } catch (BadLocationException x) { throw new IOException(x.getMessage()); } } private int gotoCommentEnd (int nTextPos) throws BadLocationException { while (nTextPos < fEnd) { char current = fDocument.getChar (nTextPos++); if (current == '*') { if ((nTextPos < fEnd) && (fDocument.getChar (nTextPos) == '/')) { ++nTextPos; return nTextPos; } } } return nTextPos; } /** * * @param delimiter * @throws BadLocationException */ private int gotoStringEnd (int nTextPos, char delimiter) throws BadLocationException { while (nTextPos < fEnd) { // If long as we are not at the end of text char current = fDocument.getChar (nTextPos++); if (current == '\\') { // ignore escaped characters ++nTextPos; } else if (current == delimiter) { return nTextPos; } } return nTextPos; // End position } /** * * @param nTextPos The current text position * * @return The position of the start of next line * * @throws BadLocationException */ private int gotoLineEnd (int nTextPos) throws BadLocationException { int line = fDocument.getLineOfOffset (nTextPos); // Get the line number of the current text position return fDocument.getLineOffset (line + 1); } private int readForwards () throws BadLocationException { while (fOffset < fEnd) { char current = fDocument.getChar(fOffset++); switch (current) { case '"': case '\'': if (fSkipStrings) { fOffset = gotoStringEnd (fOffset, current); continue; } return current; case '#': if (fSkipComments && fOffset < fEnd) { fOffset = gotoLineEnd (fOffset); continue; } return current; case '/': if (fSkipComments && fOffset < fEnd) { char next = fDocument.getChar(fOffset); if (next == '*') { // A comment starts, advance to the comment end ++fOffset; fOffset = gotoCommentEnd (fOffset); continue; } else if (next == '/') { // '//'-comment starts, advance to the line end fOffset = gotoLineEnd (fOffset); continue; } } return current; } return current; } return EOF; } /** * Check whether the current line contains a single line comment. * If it contains a single line comment (// or #), than set fOffset (the global character index) * to the character just before the single line comment. * * @throws BadLocationException */ private void handleSingleLineComment () throws BadLocationException { char current; int line = fDocument.getLineOfOffset (fOffset); // Get the line number which belongs to the current text position fOffset if (line < fCachedLineNumber) { // If we didn't parse this line already fCachedLineNumber = line; // Remember this line for the next time (so we don't search again!) int nOffset = fDocument.getLineOffset (line); // Get the start position of current line while (nOffset < fOffset) { // As long as the text position is within the current line current = fDocument.getChar (nOffset); // Get the character from the current text position switch (current) { case '/': if (fDocument.getChar (nOffset + 1) == '/') { // If the following character is '/' fOffset = nOffset - 1; return; } break; case '#': fOffset = nOffset - 1; return; case '"': // It's a string start quote case '\'': nOffset++; // Set to next character while (nOffset < fOffset) { // As long as we are within the same line char cChar = fDocument.getChar (nOffset++); if (cChar == '\\') { // Ignore escaped characters ++nOffset; } else if (cChar == current) { // If end of string found break; } } break; } nOffset++; // Go for the next character } } } /** * We search the for the block comment start sequence "/*" * * The returned value points to the '/' * * @throws BadLocationException * * @return The new text position */ private int gotoCommentStart (int nTextPos) throws BadLocationException { while (0 < nTextPos) { // As long as we are not at the start of the editor text char current = fDocument.getChar (nTextPos--); // Get the character from the current text position if ((current == '*') && // If current character is a '*' (0 <= nTextPos) && // and if we are not yet at the start of the editor text (fDocument.getChar (nTextPos) == '/')) { // and the previous character is a '/', return nTextPos; // We found the block comment start "/*" } } return nTextPos; } /** * The string closing quote has been found, when reading the editor text backwards. * So we have to search for the start of the string * * The returned value points to '"' or ''' * * @param delimiter The string double quote '"' or single ''' quote we search for * * @throws BadLocationException */ private int gotoStringStart (int nTextPos, char delimiter) throws BadLocationException { while (0 < nTextPos) { // As long as we are not at the start of the editor text char current = fDocument.getChar (nTextPos); // Get the character from the current text position if (current == delimiter) { // If we found the same character ('"' or ''') again, we have found the string start if (!(0 <= nTextPos && fDocument.getChar (nTextPos - 1) == '\\')) // If the character before the string quote is not an '/' return nTextPos; // we found the string start } --nTextPos; // Go one character back } return nTextPos; } /** * Read the editor text backwards * */ private int readBackwards() throws BadLocationException { char current; // The character on position fOffset while (0 < fOffset) { // As long as we are not at the beginning of the editor text --fOffset; // Step back one character handleSingleLineComment (); // Search for a single line comment within the current line // If there is a single line comment, fOffset is set to position just before the single line comment current = fDocument.getChar (fOffset); // Read the current character switch (current) { // Process the current character case '/': // Is it a single line comment? if (fSkipComments && // When comments should be skipped when parsing fOffset > 1) { // and if there is indeed an additional character before that '/' char prev = fDocument.getChar (fOffset - 1); // Look at the character in front of the '/' if (prev == '*') { // If '*' than a block comment ends here, advance to the comment start fOffset -= 2; // Set character pointer to character before "*/" fOffset = gotoCommentStart (fOffset); // and search for the starting "/*" of the block comment continue; } } return current; // No block comment end, so return the current character case '"': // Is it a string double quote closing '"'? case '\'': // or a string single quote closing '''? if (fSkipStrings) { // If we should skip strings when parsing --fOffset; // Set pointer one before the string closing quote fOffset = gotoStringStart (fOffset, current);// and search the start of string ('"' or ''') continue; } return current; // No string skip, so return the current character } return current; // No string and no comment } return EOF; // When the start of the text has been found } }