blob: 0663d0a62cc2ef16bd12101dabc27f2e6e6a7765 [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:front_end/src/fasta/deprecated_problems.dart';
import 'package:front_end/src/scanner/token.dart' show BeginToken, Token;
/// Provides the capability of inserting tokens into a token stream by rewriting
/// the previous token to point to the inserted token.
///
/// This class has been designed to take advantage of "previousToken" pointers
/// when they are present, but not to depend on them. When they are not
/// present, it uses heuristics to try to find the find the previous token as
/// quickly as possible by walking through tokens starting at the start of the
/// file.
class TokenStreamRewriter {
/// Synthetic token whose "next" pointer points to the first token in the
/// stream.
final Token _head;
/// The token whose "next" pointer was updated in the last call to
/// [insertTokenBefore]. This can often be used as a starting point to find
/// the a future insertion point quickly.
Token _lastPreviousToken;
/// Creates a [TokenStreamRewriter] which is prepared to rewrite the token
/// stream whose first token is [firstToken].
TokenStreamRewriter(Token firstToken)
: _head = firstToken.previous ?? (new Token.eof(-1)..next = firstToken);
/// Gets the first token in the stream (which may not be the same token that
/// was passed to the constructor, if something was inserted before it).
Token get firstToken => _head.next;
/// Inserts [newToken] into the token stream just before [insertionPoint], and
/// fixes up all "next" and "previous" pointers. Returns [newToken].
///
/// Caller is required to ensure that [insertionPoint] is actually present in
/// the token stream.
Token insertTokenBefore(Token newToken, Token insertionPoint) {
Token previous = _findPreviousToken(insertionPoint);
_lastPreviousToken = previous;
newToken.next = insertionPoint;
previous.next = newToken;
{
// Note: even though previousToken is deprecated, we need to hook it up in
// case any uses of it remain. Once previousToken is removed it should be
// safe to remove this block of code.
insertionPoint.previous = newToken;
newToken.previous = previous;
}
return newToken;
}
/// Finds the token that immediately precedes [target].
Token _findPreviousToken(Token target) {
// First see if the target has a previous token pointer. If it does, then
// we can find the previous token with no extra effort. Note: it's ok that
// we're accessing the deprecated member previousToken here, because we have
// a fallback if it is not available. Once previousToken is removed, we can
// remove the "if" test below, and always use the fallback code.
if (target.previous != null) {
return target.previous;
}
// Look for the previous token by scanning forward from [lastPreviousToken],
// if it makes sense to do so.
if (_lastPreviousToken != null &&
target.charOffset >= _lastPreviousToken.charOffset) {
Token previous = _scanForPreviousToken(target, _lastPreviousToken);
if (previous != null) return previous;
}
// Otherwise scan forward from the start of the token stream.
Token previous = _scanForPreviousToken(target, _head);
if (previous == null) {
deprecated_internalProblem('Could not find previous token');
}
return previous;
}
/// Searches for the token that immediately precedes [target], using [pos] as
/// a starting point.
///
/// Uses heuristics to skip matching `{}`, `[]`, `()`, and `<>` if possible.
///
/// If no such token is found, returns `null`.
Token _scanForPreviousToken(Token target, Token pos) {
while (!identical(pos.next, target)) {
Token nextPos;
if (pos is BeginToken &&
pos.endGroup != null &&
pos.endGroup.charOffset < target.charOffset) {
nextPos = pos.endGroup;
} else {
nextPos = pos.next;
if (nextPos == null || nextPos.charOffset > target.charOffset) {
return null;
}
}
pos = nextPos;
}
return pos;
}
}