blob: da0fd24a4ef4e1d33d0869139e7038cf77b5e74f [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CSSParserTokenStream_h
#define CSSParserTokenStream_h
#include "base/macros.h"
#include "core/css/parser/CSSParserTokenRange.h"
#include "core/css/parser/CSSTokenizer.h"
#include "platform/wtf/Noncopyable.h"
namespace blink {
namespace detail {
template <typename...>
bool IsTokenTypeOneOf(CSSParserTokenType t) {
return false;
}
template <CSSParserTokenType Head, CSSParserTokenType... Tail>
bool IsTokenTypeOneOf(CSSParserTokenType t) {
return t == Head || IsTokenTypeOneOf<Tail...>(t);
}
} // namespace detail
// A streaming interface to CSSTokenizer that tokenizes on demand.
// Abstractly, the stream ends at either EOF or the beginning/end of a block.
// To consume a block, a BlockGuard must be created first to ensure that
// we finish consuming a block even if there was an error.
//
// Methods prefixed with "Unchecked" can only be called after calls to Peek(),
// EnsureLookAhead(), or AtEnd() with no subsequent modifications to the stream
// such as a consume.
class CORE_EXPORT CSSParserTokenStream {
DISALLOW_NEW();
public:
// Instantiate this to start reading from a block. When the guard is out of
// scope, the rest of the block is consumed.
class BlockGuard {
public:
explicit BlockGuard(CSSParserTokenStream& stream) : stream_(stream) {
const CSSParserToken next = stream.ConsumeInternal();
DCHECK_EQ(next.GetBlockType(), CSSParserToken::kBlockStart);
}
~BlockGuard() {
stream_.EnsureLookAhead();
stream_.UncheckedSkipToEndOfBlock();
}
private:
CSSParserTokenStream& stream_;
};
// We found that this value works well empirically by printing out the
// maximum buffer size for a few top alexa websites. It should be slightly
// above the expected number of tokens in the prelude of an at rule and
// the number of tokens in a declaration.
// TODO(crbug.com/661854): Can we streamify at rule parsing so that this is
// only needed for declarations which are easier to think about?
static constexpr size_t InitialBufferSize() { return 128; }
explicit CSSParserTokenStream(CSSTokenizer& tokenizer)
: buffer_(InitialBufferSize()), tokenizer_(tokenizer), next_(kEOFToken) {}
CSSParserTokenStream(CSSParserTokenStream&&) = default;
inline void EnsureLookAhead() {
if (!HasLookAhead()) {
has_look_ahead_ = true;
next_ = tokenizer_.TokenizeSingle();
}
}
// Forcibly read a lookahead token.
inline void LookAhead() {
DCHECK(!HasLookAhead());
next_ = tokenizer_.TokenizeSingle();
has_look_ahead_ = true;
}
inline bool HasLookAhead() const { return has_look_ahead_; }
inline const CSSParserToken& Peek() {
EnsureLookAhead();
return next_;
}
inline const CSSParserToken& UncheckedPeek() const {
DCHECK(HasLookAhead());
return next_;
}
inline const CSSParserToken& Consume() {
EnsureLookAhead();
return UncheckedConsume();
}
const CSSParserToken& UncheckedConsume() {
DCHECK(HasLookAhead());
DCHECK_NE(next_.GetBlockType(), CSSParserToken::kBlockStart);
DCHECK_NE(next_.GetBlockType(), CSSParserToken::kBlockEnd);
has_look_ahead_ = false;
offset_ = tokenizer_.Offset();
return next_;
}
inline bool AtEnd() {
EnsureLookAhead();
return UncheckedAtEnd();
}
inline bool UncheckedAtEnd() const {
DCHECK(HasLookAhead());
return next_.IsEOF() || next_.GetBlockType() == CSSParserToken::kBlockEnd;
}
// Get the index of the character in the original string to be consumed next.
size_t Offset() const { return offset_; }
// Get the index of the starting character of the look-ahead token.
size_t LookAheadOffset() const {
DCHECK(HasLookAhead());
return tokenizer_.PreviousOffset();
}
void ConsumeWhitespace();
CSSParserToken ConsumeIncludingWhitespace();
void UncheckedConsumeComponentValue();
// Either consumes a comment token and returns true, or peeks at the next
// token and return false.
bool ConsumeCommentOrNothing();
// Invalidates any ranges created by previous calls to this function
template <CSSParserTokenType... Types>
CSSParserTokenRange ConsumeUntilPeekedTypeIs() {
EnsureLookAhead();
buffer_.clear();
while (!UncheckedAtEnd() &&
!detail::IsTokenTypeOneOf<Types...>(UncheckedPeek().GetType())) {
// Have to use internal consume/peek in here because they can read past
// start/end of blocks
unsigned nesting_level = 0;
do {
const CSSParserToken& token = UncheckedConsumeInternal();
buffer_.push_back(token);
if (token.GetBlockType() == CSSParserToken::kBlockStart)
nesting_level++;
else if (token.GetBlockType() == CSSParserToken::kBlockEnd)
nesting_level--;
} while (!PeekInternal().IsEOF() && nesting_level);
}
return buffer_.Range();
}
private:
// Used to store tokens for CSSParserTokenRanges.
// FIXME: Determine if this improves speed at all compared to allocating a
// fresh vector each time.
class TokenBuffer {
public:
TokenBuffer(size_t capacity) { tokens_.ReserveInitialCapacity(capacity); }
void clear() { size_ = 0; }
void push_back(const CSSParserToken& token) {
if (size_ < tokens_.size())
tokens_[size_] = token;
else
tokens_.push_back(token);
++size_;
}
CSSParserTokenRange Range() const {
return CSSParserTokenRange(tokens_).MakeSubRange(tokens_.begin(),
tokens_.begin() + size_);
}
private:
Vector<CSSParserToken, 32> tokens_;
size_t size_ = 0;
};
const CSSParserToken& PeekInternal() {
EnsureLookAhead();
return UncheckedPeekInternal();
}
const CSSParserToken& UncheckedPeekInternal() const {
DCHECK(HasLookAhead());
return next_;
}
const CSSParserToken& ConsumeInternal() {
EnsureLookAhead();
return UncheckedConsumeInternal();
}
const CSSParserToken& UncheckedConsumeInternal() {
DCHECK(HasLookAhead());
has_look_ahead_ = false;
offset_ = tokenizer_.Offset();
return next_;
}
void UncheckedSkipToEndOfBlock();
TokenBuffer buffer_;
CSSTokenizer& tokenizer_;
CSSParserToken next_;
size_t offset_ = 0;
bool has_look_ahead_ = false;
DISALLOW_COPY_AND_ASSIGN(CSSParserTokenStream);
};
} // namespace blink
#endif // CSSParserTokenStream_h