| /* |
| * Copyright (c) 2012, the Dart project authors. |
| * |
| * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except |
| * in compliance with the License. You may obtain a copy of the License at |
| * |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Unless required by applicable law or agreed to in writing, software distributed under the License |
| * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
| * or implied. See the License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.dart.engine.scanner; |
| |
| import com.google.dart.engine.error.AnalysisErrorListener; |
| import com.google.dart.engine.source.Source; |
| import com.google.dart.engine.utilities.collection.TokenMap; |
| |
| /** |
| * Instances of the class {@code IncrementalScanner} implement a scanner that scans a subset of a |
| * string and inserts the resulting tokens into the middle of an existing token stream. |
| * |
| * @coverage dart.engine.parser |
| */ |
| public class IncrementalScanner extends Scanner { |
| /** |
| * The reader used to access the characters in the source. |
| */ |
| private CharacterReader reader; |
| |
| /** |
| * A map from tokens that were copied to the copies of the tokens. |
| */ |
| private TokenMap tokenMap = new TokenMap(); |
| |
| /** |
| * The token in the new token stream immediately to the left of the range of tokens that were |
| * inserted, or the token immediately to the left of the modified region if there were no new |
| * tokens. |
| */ |
| private Token leftToken; |
| |
| /** |
| * The token in the new token stream immediately to the right of the range of tokens that were |
| * inserted, or the token immediately to the right of the modified region if there were no new |
| * tokens. |
| */ |
| private Token rightToken; |
| |
| /** |
| * A flag indicating whether there were any tokens changed as a result of the modification. |
| */ |
| private boolean hasNonWhitespaceChange = false; |
| |
| /** |
| * Initialize a newly created scanner. |
| * |
| * @param source the source being scanned |
| * @param reader the character reader used to read the characters in the source |
| * @param errorListener the error listener that will be informed of any errors that are found |
| */ |
| public IncrementalScanner(Source source, CharacterReader reader, |
| AnalysisErrorListener errorListener) { |
| super(source, reader, errorListener); |
| this.reader = reader; |
| } |
| |
| /** |
| * Return the token in the new token stream immediately to the left of the range of tokens that |
| * were inserted, or the token immediately to the left of the modified region if there were no new |
| * tokens. |
| * |
| * @return the token to the left of the inserted tokens |
| */ |
| public Token getLeftToken() { |
| return leftToken; |
| } |
| |
| /** |
| * Return the token in the new token stream immediately to the right of the range of tokens that |
| * were inserted, or the token immediately to the right of the modified region if there were no |
| * new tokens. |
| * |
| * @return the token to the right of the inserted tokens |
| */ |
| public Token getRightToken() { |
| return rightToken; |
| } |
| |
| /** |
| * Return a map from tokens that were copied to the copies of the tokens. |
| * |
| * @return a map from tokens that were copied to the copies of the tokens |
| */ |
| public TokenMap getTokenMap() { |
| return tokenMap; |
| } |
| |
| /** |
| * Return {@code true} if there were any tokens either added or removed (or both) as a result of |
| * the modification. |
| * |
| * @return {@code true} if there were any tokens changed as a result of the modification |
| */ |
| public boolean hasNonWhitespaceChange() { |
| return hasNonWhitespaceChange; |
| } |
| |
| /** |
| * Given the stream of tokens scanned from the original source, the modified source (the result of |
| * replacing one contiguous range of characters with another string of characters), and a |
| * specification of the modification that was made, return a stream of tokens scanned from the |
| * modified source. The original stream of tokens will not be modified. |
| * |
| * @param originalStream the stream of tokens scanned from the original source |
| * @param index the index of the first character in both the original and modified source that was |
| * affected by the modification |
| * @param removedLength the number of characters removed from the original source |
| * @param insertedLength the number of characters added to the modified source |
| */ |
| public Token rescan(Token originalStream, int index, int removedLength, int insertedLength) { |
| // |
| // Copy all of the tokens in the originalStream whose end is less than the replacement start. |
| // (If the replacement start is equal to the end of an existing token, then it means that the |
| // existing token might have been modified, so we need to rescan it.) |
| // |
| while (originalStream.getType() != TokenType.EOF && originalStream.getEnd() < index) { |
| originalStream = copyAndAdvance(originalStream, 0); |
| } |
| Token oldFirst = originalStream; |
| Token oldLeftToken = originalStream.getPrevious(); |
| leftToken = getTail(); |
| // |
| // Skip tokens in the original stream until we find a token whose offset is greater than the end |
| // of the removed region. (If the end of the removed region is equal to the beginning of an |
| // existing token, then it means that the existing token might have been modified, so we need to |
| // rescan it.) |
| // |
| int removedEnd = index + (removedLength == 0 ? 0 : removedLength - 1); |
| while (originalStream.getType() != TokenType.EOF && originalStream.getOffset() <= removedEnd) { |
| originalStream = originalStream.getNext(); |
| } |
| Token oldLast; |
| Token oldRightToken; |
| if (originalStream.getType() != TokenType.EOF && removedEnd + 1 == originalStream.getOffset()) { |
| oldLast = originalStream; |
| originalStream = originalStream.getNext(); |
| oldRightToken = originalStream; |
| } else { |
| oldLast = originalStream.getPrevious(); |
| oldRightToken = originalStream; |
| } |
| // |
| // Compute the delta between the character index of characters after the modified region in the |
| // original source and the index of the corresponding character in the modified source. |
| // |
| int delta = insertedLength - removedLength; |
| // |
| // Compute the range of characters that are known to need to be rescanned. If the index is |
| // within an existing token, then we need to start at the beginning of the token. |
| // |
| int scanStart = Math.min(oldFirst.getOffset(), index); |
| int oldEnd = oldLast.getEnd() + delta - 1; |
| int newEnd = index + insertedLength - 1; |
| int scanEnd = Math.max(newEnd, oldEnd); |
| // |
| // Starting at the start of the scan region, scan tokens from the modifiedSource until the end |
| // of the just scanned token is greater than or equal to end of the scan region in the modified |
| // source. Include trailing characters of any token that was split as a result of inserted text, |
| // as in "ab" --> "a.b". |
| // |
| reader.setOffset(scanStart - 1); |
| int next = reader.advance(); |
| while (next != -1 && reader.getOffset() <= scanEnd) { |
| next = bigSwitch(next); |
| } |
| // |
| // Copy the remaining tokens in the original stream, but apply the delta to the token's offset. |
| // |
| if (originalStream.getType() == TokenType.EOF) { |
| copyAndAdvance(originalStream, delta); |
| rightToken = getTail(); |
| rightToken.setNextWithoutSettingPrevious(rightToken); |
| } else { |
| originalStream = copyAndAdvance(originalStream, delta); |
| rightToken = getTail(); |
| while (originalStream.getType() != TokenType.EOF) { |
| originalStream = copyAndAdvance(originalStream, delta); |
| } |
| Token eof = copyAndAdvance(originalStream, delta); |
| eof.setNextWithoutSettingPrevious(eof); |
| } |
| // |
| // If the index is immediately after an existing token and the inserted characters did not |
| // change that original token, then adjust the leftToken to be the next token. For example, in |
| // "a; c;" --> "a;b c;", the leftToken was ";", but this code advances it to "b" since "b" is |
| // the first new token. |
| // |
| Token newFirst = leftToken.getNext(); |
| while (newFirst != rightToken && oldFirst != oldRightToken |
| && newFirst.getType() != TokenType.EOF && equalTokens(oldFirst, newFirst)) { |
| tokenMap.put(oldFirst, newFirst); |
| oldLeftToken = oldFirst; |
| oldFirst = oldFirst.getNext(); |
| leftToken = newFirst; |
| newFirst = newFirst.getNext(); |
| } |
| Token newLast = rightToken.getPrevious(); |
| while (newLast != leftToken && oldLast != oldLeftToken && newLast.getType() != TokenType.EOF |
| && equalTokens(oldLast, newLast)) { |
| tokenMap.put(oldLast, newLast); |
| oldRightToken = oldLast; |
| oldLast = oldLast.getPrevious(); |
| rightToken = newLast; |
| newLast = newLast.getPrevious(); |
| } |
| hasNonWhitespaceChange = leftToken.getNext() != rightToken |
| || oldLeftToken.getNext() != oldRightToken; |
| // |
| // TODO(brianwilkerson) Begin tokens are not getting associated with the corresponding end |
| // tokens (because the end tokens have not been copied when we're copying the begin tokens). |
| // This could have implications for parsing. |
| // TODO(brianwilkerson) Update the lineInfo. |
| // |
| return getFirstToken(); |
| } |
| |
| private Token copyAndAdvance(Token originalToken, int delta) { |
| Token copiedToken = originalToken.copy(); |
| tokenMap.put(originalToken, copiedToken); |
| copiedToken.applyDelta(delta); |
| appendToken(copiedToken); |
| |
| Token originalComment = originalToken.getPrecedingComments(); |
| Token copiedComment = originalToken.getPrecedingComments(); |
| while (originalComment != null) { |
| tokenMap.put(originalComment, copiedComment); |
| originalComment = originalComment.getNext(); |
| copiedComment = copiedComment.getNext(); |
| } |
| return originalToken.getNext(); |
| } |
| |
| /** |
| * Return {@code true} if the two tokens are equal to each other. For the purposes of the |
| * incremental scanner, two tokens are equal if they have the same type and lexeme. |
| * |
| * @param oldToken the token from the old stream that is being compared |
| * @param newToken the token from the new stream that is being compared |
| * @return {@code true} if the two tokens are equal to each other |
| */ |
| private boolean equalTokens(Token oldToken, Token newToken) { |
| return oldToken.getType() == newToken.getType() && oldToken.getLength() == newToken.getLength() |
| && oldToken.getLexeme().equals(newToken.getLexeme()); |
| } |
| } |