diff options
Diffstat (limited to 'image/StreamingLexer.h')
-rw-r--r-- | image/StreamingLexer.h | 750 |
1 files changed, 750 insertions, 0 deletions
diff --git a/image/StreamingLexer.h b/image/StreamingLexer.h new file mode 100644 index 000000000..e4abdb718 --- /dev/null +++ b/image/StreamingLexer.h @@ -0,0 +1,750 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * StreamingLexer is a lexing framework designed to make it simple to write + * image decoders without worrying about the details of how the data is arriving + * from the network. + */ + +#ifndef mozilla_image_StreamingLexer_h +#define mozilla_image_StreamingLexer_h + +#include <algorithm> +#include <cstdint> +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/Move.h" +#include "mozilla/Variant.h" +#include "mozilla/Vector.h" + +namespace mozilla { +namespace image { + +/// Buffering behaviors for StreamingLexer transitions. +enum class BufferingStrategy +{ + BUFFERED, // Data will be buffered and processed in one chunk. + UNBUFFERED // Data will be processed as it arrives, in multiple chunks. +}; + +/// Control flow behaviors for StreamingLexer transitions. +enum class ControlFlowStrategy +{ + CONTINUE, // If there's enough data, proceed to the next state immediately. + YIELD // Yield to the caller before proceeding to the next state. +}; + +/// Possible terminal states for the lexer. +enum class TerminalState +{ + SUCCESS, + FAILURE +}; + +/// Possible yield reasons for the lexer. +enum class Yield +{ + NEED_MORE_DATA, // The lexer cannot continue without more data. + OUTPUT_AVAILABLE // There is output available for the caller to consume. +}; + +/// The result of a call to StreamingLexer::Lex(). +typedef Variant<TerminalState, Yield> LexerResult; + +/** + * LexerTransition is a type used to give commands to the lexing framework. + * Code that uses StreamingLexer can create LexerTransition values using the + * static methods on Transition, and then return them to the lexing framework + * for execution. + */ +template <typename State> +class LexerTransition +{ +public: + // This is implicit so that Terminate{Success,Failure}() can return a + // TerminalState and have it implicitly converted to a + // LexerTransition<State>, which avoids the need for a "<State>" + // qualification to the Terminate{Success,Failure}() callsite. + MOZ_IMPLICIT LexerTransition(TerminalState aFinalState) + : mNextState(aFinalState) + {} + + bool NextStateIsTerminal() const + { + return mNextState.template is<TerminalState>(); + } + + TerminalState NextStateAsTerminal() const + { + return mNextState.template as<TerminalState>(); + } + + State NextState() const + { + return mNextState.template as<NonTerminalState>().mState; + } + + State UnbufferedState() const + { + return *mNextState.template as<NonTerminalState>().mUnbufferedState; + } + + size_t Size() const + { + return mNextState.template as<NonTerminalState>().mSize; + } + + BufferingStrategy Buffering() const + { + return mNextState.template as<NonTerminalState>().mBufferingStrategy; + } + + ControlFlowStrategy ControlFlow() const + { + return mNextState.template as<NonTerminalState>().mControlFlowStrategy; + } + +private: + friend struct Transition; + + LexerTransition(State aNextState, + const Maybe<State>& aUnbufferedState, + size_t aSize, + BufferingStrategy aBufferingStrategy, + ControlFlowStrategy aControlFlowStrategy) + : mNextState(NonTerminalState(aNextState, aUnbufferedState, aSize, + aBufferingStrategy, aControlFlowStrategy)) + {} + + struct NonTerminalState + { + State mState; + Maybe<State> mUnbufferedState; + size_t mSize; + BufferingStrategy mBufferingStrategy; + ControlFlowStrategy mControlFlowStrategy; + + NonTerminalState(State aState, + const Maybe<State>& aUnbufferedState, + size_t aSize, + BufferingStrategy aBufferingStrategy, + ControlFlowStrategy aControlFlowStrategy) + : mState(aState) + , mUnbufferedState(aUnbufferedState) + , mSize(aSize) + , mBufferingStrategy(aBufferingStrategy) + , mControlFlowStrategy(aControlFlowStrategy) + { + MOZ_ASSERT_IF(mBufferingStrategy == BufferingStrategy::UNBUFFERED, + mUnbufferedState); + MOZ_ASSERT_IF(mUnbufferedState, + mBufferingStrategy == BufferingStrategy::UNBUFFERED); + } + }; + + Variant<NonTerminalState, TerminalState> mNextState; +}; + +struct Transition +{ + /// Transition to @aNextState, buffering @aSize bytes of data. + template <typename State> + static LexerTransition<State> + To(const State& aNextState, size_t aSize) + { + return LexerTransition<State>(aNextState, Nothing(), aSize, + BufferingStrategy::BUFFERED, + ControlFlowStrategy::CONTINUE); + } + + /// Yield to the caller, transitioning to @aNextState when Lex() is next + /// invoked. The same data that was delivered for the current state will be + /// delivered again. + template <typename State> + static LexerTransition<State> + ToAfterYield(const State& aNextState) + { + return LexerTransition<State>(aNextState, Nothing(), 0, + BufferingStrategy::BUFFERED, + ControlFlowStrategy::YIELD); + } + + /** + * Transition to @aNextState via @aUnbufferedState, reading @aSize bytes of + * data unbuffered. + * + * The unbuffered data will be delivered in state @aUnbufferedState, which may + * be invoked repeatedly until all @aSize bytes have been delivered. Then, + * @aNextState will be invoked with no data. No state transitions are allowed + * from @aUnbufferedState except for transitions to a terminal state, so + * @aNextState will always be reached unless lexing terminates early. + */ + template <typename State> + static LexerTransition<State> + ToUnbuffered(const State& aNextState, + const State& aUnbufferedState, + size_t aSize) + { + return LexerTransition<State>(aNextState, Some(aUnbufferedState), aSize, + BufferingStrategy::UNBUFFERED, + ControlFlowStrategy::CONTINUE); + } + + /** + * Continue receiving unbuffered data. @aUnbufferedState should be the same + * state as the @aUnbufferedState specified in the preceding call to + * ToUnbuffered(). + * + * This should be used during an unbuffered read initiated by ToUnbuffered(). + */ + template <typename State> + static LexerTransition<State> + ContinueUnbuffered(const State& aUnbufferedState) + { + return LexerTransition<State>(aUnbufferedState, Nothing(), 0, + BufferingStrategy::BUFFERED, + ControlFlowStrategy::CONTINUE); + } + + /** + * Continue receiving unbuffered data. @aUnbufferedState should be the same + * state as the @aUnbufferedState specified in the preceding call to + * ToUnbuffered(). @aSize indicates the amount of data that has already been + * consumed; the next state will receive the same data that was delivered to + * the current state, without the first @aSize bytes. + * + * This should be used during an unbuffered read initiated by ToUnbuffered(). + */ + template <typename State> + static LexerTransition<State> + ContinueUnbufferedAfterYield(const State& aUnbufferedState, size_t aSize) + { + return LexerTransition<State>(aUnbufferedState, Nothing(), aSize, + BufferingStrategy::BUFFERED, + ControlFlowStrategy::YIELD); + } + + /** + * Terminate lexing, ending up in terminal state SUCCESS. (The implicit + * LexerTransition constructor will convert the result to a LexerTransition + * as needed.) + * + * No more data will be delivered after this function is used. + */ + static TerminalState + TerminateSuccess() + { + return TerminalState::SUCCESS; + } + + /** + * Terminate lexing, ending up in terminal state FAILURE. (The implicit + * LexerTransition constructor will convert the result to a LexerTransition + * as needed.) + * + * No more data will be delivered after this function is used. + */ + static TerminalState + TerminateFailure() + { + return TerminalState::FAILURE; + } + +private: + Transition(); +}; + +/** + * StreamingLexer is a lexing framework designed to make it simple to write + * image decoders without worrying about the details of how the data is arriving + * from the network. + * + * To use StreamingLexer: + * + * - Create a State type. This should be an |enum class| listing all of the + * states that you can be in while lexing the image format you're trying to + * read. + * + * - Add an instance of StreamingLexer<State> to your decoder class. Initialize + * it with a Transition::To() the state that you want to start lexing in, and + * a Transition::To() the state you'd like to use to handle truncated data. + * + * - In your decoder's DoDecode() method, call Lex(), passing in the input + * data and length that are passed to DoDecode(). You also need to pass + * a lambda which dispatches to lexing code for each state based on the State + * value that's passed in. The lambda generally should just continue a + * |switch| statement that calls different methods for each State value. Each + * method should return a LexerTransition<State>, which the lambda should + * return in turn. + * + * - Write the methods that actually implement lexing for your image format. + * These methods should return either Transition::To(), to move on to another + * state, or Transition::Terminate{Success,Failure}(), if lexing has + * terminated in either success or failure. (There are also additional + * transitions for unbuffered reads; see below.) + * + * That's the basics. The StreamingLexer will track your position in the input + * and buffer enough data so that your lexing methods can process everything in + * one pass. Lex() returns Yield::NEED_MORE_DATA if more data is needed, in + * which case you should just return from DoDecode(). If lexing reaches a + * terminal state, Lex() returns TerminalState::SUCCESS or + * TerminalState::FAILURE, and you can check which one to determine if lexing + * succeeded or failed and do any necessary cleanup. + * + * Sometimes, the input data is truncated. StreamingLexer will notify you when + * this happens by invoking the truncated data state you passed to the + * constructor. At this point you can attempt to recover and return + * TerminalState::SUCCESS or TerminalState::FAILURE, depending on whether you + * were successful. Note that you can't return anything other than a terminal + * state in this situation, since there's no more data to read. For the same + * reason, your truncated data state shouldn't require any data. (That is, the + * @aSize argument you pass to Transition::To() must be zero.) Violating these + * requirements will trigger assertions and an immediate transition to + * TerminalState::FAILURE. + * + * Some lexers may want to *avoid* buffering in some cases, and just process the + * data as it comes in. This is useful if, for example, you just want to skip + * over a large section of data; there's no point in buffering data you're just + * going to ignore. + * + * You can begin an unbuffered read with Transition::ToUnbuffered(). This works + * a little differently than Transition::To() in that you specify *two* states. + * The @aUnbufferedState argument specifies a state that will be called + * repeatedly with unbuffered data, as soon as it arrives. The implementation + * for that state should return either a transition to a terminal state, or a + * Transition::ContinueUnbuffered() to the same @aUnbufferedState. (From a + * technical perspective, it's not necessary to specify the state again, but + * it's helpful to human readers.) Once the amount of data requested in the + * original call to Transition::ToUnbuffered() has been delivered, Lex() will + * transition to the @aNextState state specified via Transition::ToUnbuffered(). + * That state will be invoked with *no* data; it's just called to signal that + * the unbuffered read is over. + * + * It's sometimes useful for a lexer to provide incremental results, rather + * than simply running to completion and presenting all its output at once. For + * example, when decoding animated images, it may be useful to produce each + * frame incrementally. StreamingLexer supports this by allowing a lexer to + * yield. + * + * To yield back to the caller, a state implementation can simply return + * Transition::ToAfterYield(). ToAfterYield()'s @aNextState argument specifies + * the next state that the lexer should transition to, just like when using + * Transition::To(), but there are two differences. One is that Lex() will + * return to the caller before processing any more data when it encounters a + * yield transition. This provides an opportunity for the caller to interact with the + * lexer's intermediate results. The second difference is that @aNextState + * will be called with *the same data as the state that you returned + * Transition::ToAfterYield() from*. This allows a lexer to partially consume + * the data, return intermediate results, and then finish consuming the data + * when @aNextState is called. + * + * It's also possible to yield during an unbuffered read. Just return a + * Transition::ContinueUnbufferedAfterYield(). Just like with + * Transition::ContinueUnbuffered(), the @aUnbufferedState must be the same as + * the one originally passed to Transition::ToUnbuffered(). The second argument, + * @aSize, specifies the amount of data that the lexer has already consumed. + * When @aUnbufferedState is next invoked, it will get the same data that it + * received previously, except that the first @aSize bytes will be excluded. + * This makes it easy to consume unbuffered data incrementally. + * + * XXX(seth): We should be able to get of the |State| stuff totally once bug + * 1198451 lands, since we can then just return a function representing the next + * state directly. + */ +template <typename State, size_t InlineBufferSize = 16> +class StreamingLexer +{ +public: + StreamingLexer(LexerTransition<State> aStartState, + LexerTransition<State> aTruncatedState) + : mTransition(TerminalState::FAILURE) + , mTruncatedTransition(aTruncatedState) + { + if (!aStartState.NextStateIsTerminal() && + aStartState.ControlFlow() == ControlFlowStrategy::YIELD) { + // Allowing a StreamingLexer to start in a yield state doesn't make sense + // semantically (since yield states are supposed to deliver the same data + // as previous states, and there's no previous state here), but more + // importantly, it's necessary to advance a SourceBufferIterator at least + // once before you can read from it, and adding the necessary checks to + // Lex() to avoid that issue has the potential to mask real bugs. So + // instead, it's better to forbid starting in a yield state. + MOZ_ASSERT_UNREACHABLE("Starting in a yield state"); + return; + } + + if (!aTruncatedState.NextStateIsTerminal() && + (aTruncatedState.ControlFlow() == ControlFlowStrategy::YIELD || + aTruncatedState.Buffering() == BufferingStrategy::UNBUFFERED || + aTruncatedState.Size() != 0)) { + // The truncated state can't receive any data because, by definition, + // there is no more data to receive. That means that yielding or an + // unbuffered read would not make sense, and that the state must require + // zero bytes. + MOZ_ASSERT_UNREACHABLE("Truncated state makes no sense"); + return; + } + + SetTransition(aStartState); + } + + template <typename Func> + LexerResult Lex(SourceBufferIterator& aIterator, + IResumable* aOnResume, + Func aFunc) + { + if (mTransition.NextStateIsTerminal()) { + // We've already reached a terminal state. We never deliver any more data + // in this case; just return the terminal state again immediately. + return LexerResult(mTransition.NextStateAsTerminal()); + } + + Maybe<LexerResult> result; + + // If the lexer requested a yield last time, we deliver the same data again + // before we read anything else from |aIterator|. Note that although to the + // callers of Lex(), Yield::NEED_MORE_DATA is just another type of yield, + // internally they're different in that we don't redeliver the same data in + // the Yield::NEED_MORE_DATA case, and |mYieldingToState| is not set. This + // means that for Yield::NEED_MORE_DATA, we go directly to the loop below. + if (mYieldingToState) { + result = mTransition.Buffering() == BufferingStrategy::UNBUFFERED + ? UnbufferedReadAfterYield(aIterator, aFunc) + : BufferedReadAfterYield(aIterator, aFunc); + } + + while (!result) { + MOZ_ASSERT_IF(mTransition.Buffering() == BufferingStrategy::UNBUFFERED, + mUnbufferedState); + + // Figure out how much we need to read. + const size_t toRead = mTransition.Buffering() == BufferingStrategy::UNBUFFERED + ? mUnbufferedState->mBytesRemaining + : mTransition.Size() - mBuffer.length(); + + // Attempt to advance the iterator by |toRead| bytes. + switch (aIterator.AdvanceOrScheduleResume(toRead, aOnResume)) { + case SourceBufferIterator::WAITING: + // We can't continue because the rest of the data hasn't arrived from + // the network yet. We don't have to do anything special; the + // SourceBufferIterator will ensure that |aOnResume| gets called when + // more data is available. + result = Some(LexerResult(Yield::NEED_MORE_DATA)); + break; + + case SourceBufferIterator::COMPLETE: + // The data is truncated; if not, the lexer would've reached a + // terminal state by now. We only get to + // SourceBufferIterator::COMPLETE after every byte of data has been + // delivered to the lexer. + result = Truncated(aIterator, aFunc); + break; + + case SourceBufferIterator::READY: + // Process the new data that became available. + MOZ_ASSERT(aIterator.Data()); + + result = mTransition.Buffering() == BufferingStrategy::UNBUFFERED + ? UnbufferedRead(aIterator, aFunc) + : BufferedRead(aIterator, aFunc); + break; + + default: + MOZ_ASSERT_UNREACHABLE("Unknown SourceBufferIterator state"); + result = SetTransition(Transition::TerminateFailure()); + } + }; + + return *result; + } + +private: + template <typename Func> + Maybe<LexerResult> UnbufferedRead(SourceBufferIterator& aIterator, Func aFunc) + { + MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::UNBUFFERED); + MOZ_ASSERT(mUnbufferedState); + MOZ_ASSERT(!mYieldingToState); + MOZ_ASSERT(mBuffer.empty(), + "Buffered read at the same time as unbuffered read?"); + MOZ_ASSERT(aIterator.Length() <= mUnbufferedState->mBytesRemaining, + "Read too much data during unbuffered read?"); + MOZ_ASSERT(mUnbufferedState->mBytesConsumedInCurrentChunk == 0, + "Already consumed data in the current chunk, but not yielding?"); + + if (mUnbufferedState->mBytesRemaining == 0) { + // We're done with the unbuffered read, so transition to the next state. + return SetTransition(aFunc(mTransition.NextState(), nullptr, 0)); + } + + return ContinueUnbufferedRead(aIterator.Data(), aIterator.Length(), + aIterator.Length(), aFunc); + } + + template <typename Func> + Maybe<LexerResult> UnbufferedReadAfterYield(SourceBufferIterator& aIterator, Func aFunc) + { + MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::UNBUFFERED); + MOZ_ASSERT(mUnbufferedState); + MOZ_ASSERT(mYieldingToState); + MOZ_ASSERT(mBuffer.empty(), + "Buffered read at the same time as unbuffered read?"); + MOZ_ASSERT(aIterator.Length() <= mUnbufferedState->mBytesRemaining, + "Read too much data during unbuffered read?"); + MOZ_ASSERT(mUnbufferedState->mBytesConsumedInCurrentChunk <= aIterator.Length(), + "Consumed more data than the current chunk holds?"); + MOZ_ASSERT(mTransition.UnbufferedState() == *mYieldingToState); + + mYieldingToState = Nothing(); + + if (mUnbufferedState->mBytesRemaining == 0) { + // We're done with the unbuffered read, so transition to the next state. + return SetTransition(aFunc(mTransition.NextState(), nullptr, 0)); + } + + // Since we've yielded, we may have already consumed some data in this + // chunk. Make the necessary adjustments. (Note that the std::min call is + // just belt-and-suspenders to keep this code memory safe even if there's + // a bug somewhere.) + const size_t toSkip = + std::min(mUnbufferedState->mBytesConsumedInCurrentChunk, aIterator.Length()); + const char* data = aIterator.Data() + toSkip; + const size_t length = aIterator.Length() - toSkip; + + // If |length| is zero, we've hit the end of the current chunk. This only + // happens if we yield right at the end of a chunk. Rather than call |aFunc| + // with a |length| of zero bytes (which seems potentially surprising to + // decoder authors), we go ahead and read more data. + if (length == 0) { + return FinishCurrentChunkOfUnbufferedRead(aIterator.Length()); + } + + return ContinueUnbufferedRead(data, length, aIterator.Length(), aFunc); + } + + template <typename Func> + Maybe<LexerResult> ContinueUnbufferedRead(const char* aData, + size_t aLength, + size_t aChunkLength, + Func aFunc) + { + // Call aFunc with the unbuffered state to indicate that we're in the + // middle of an unbuffered read. We enforce that any state transition + // passed back to us is either a terminal state or takes us back to the + // unbuffered state. + LexerTransition<State> unbufferedTransition = + aFunc(mTransition.UnbufferedState(), aData, aLength); + + // If we reached a terminal state, we're done. + if (unbufferedTransition.NextStateIsTerminal()) { + return SetTransition(unbufferedTransition); + } + + MOZ_ASSERT(mTransition.UnbufferedState() == + unbufferedTransition.NextState()); + + // Perform bookkeeping. + if (unbufferedTransition.ControlFlow() == ControlFlowStrategy::YIELD) { + mUnbufferedState->mBytesConsumedInCurrentChunk += unbufferedTransition.Size(); + return SetTransition(unbufferedTransition); + } + + MOZ_ASSERT(unbufferedTransition.Size() == 0); + return FinishCurrentChunkOfUnbufferedRead(aChunkLength); + } + + Maybe<LexerResult> FinishCurrentChunkOfUnbufferedRead(size_t aChunkLength) + { + // We've finished an unbuffered read of a chunk of length |aChunkLength|, so + // update |myBytesRemaining| to reflect that we're |aChunkLength| closer to + // the end of the unbuffered read. (The std::min call is just + // belt-and-suspenders to keep this code memory safe even if there's a bug + // somewhere.) + mUnbufferedState->mBytesRemaining -= + std::min(mUnbufferedState->mBytesRemaining, aChunkLength); + + // Since we're moving on to a new chunk, we can forget about the count of + // bytes consumed by yielding in the current chunk. + mUnbufferedState->mBytesConsumedInCurrentChunk = 0; + + return Nothing(); // Keep processing. + } + + template <typename Func> + Maybe<LexerResult> BufferedRead(SourceBufferIterator& aIterator, Func aFunc) + { + MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::BUFFERED); + MOZ_ASSERT(!mYieldingToState); + MOZ_ASSERT(!mUnbufferedState, + "Buffered read at the same time as unbuffered read?"); + MOZ_ASSERT(mBuffer.length() < mTransition.Size() || + (mBuffer.length() == 0 && mTransition.Size() == 0), + "Buffered more than we needed?"); + + // If we have all the data, we don't actually need to buffer anything. + if (mBuffer.empty() && aIterator.Length() == mTransition.Size()) { + return SetTransition(aFunc(mTransition.NextState(), + aIterator.Data(), + aIterator.Length())); + } + + // We do need to buffer, so make sure the buffer has enough capacity. We + // deliberately wait until we know for sure we need to buffer to call + // reserve() since it could require memory allocation. + if (!mBuffer.reserve(mTransition.Size())) { + return SetTransition(Transition::TerminateFailure()); + } + + // Append the new data we just got to the buffer. + if (!mBuffer.append(aIterator.Data(), aIterator.Length())) { + return SetTransition(Transition::TerminateFailure()); + } + + if (mBuffer.length() != mTransition.Size()) { + return Nothing(); // Keep processing. + } + + // We've buffered everything, so transition to the next state. + return SetTransition(aFunc(mTransition.NextState(), + mBuffer.begin(), + mBuffer.length())); + } + + template <typename Func> + Maybe<LexerResult> BufferedReadAfterYield(SourceBufferIterator& aIterator, + Func aFunc) + { + MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::BUFFERED); + MOZ_ASSERT(mYieldingToState); + MOZ_ASSERT(!mUnbufferedState, + "Buffered read at the same time as unbuffered read?"); + MOZ_ASSERT(mBuffer.length() <= mTransition.Size(), + "Buffered more than we needed?"); + + State nextState = Move(*mYieldingToState); + + // After a yield, we need to take the same data that we delivered to the + // last state, and deliver it again to the new state. We know that this is + // happening right at a state transition, and that the last state was a + // buffered read, so there are two cases: + + // 1. We got the data from the SourceBufferIterator directly. + if (mBuffer.empty() && aIterator.Length() == mTransition.Size()) { + return SetTransition(aFunc(nextState, + aIterator.Data(), + aIterator.Length())); + } + + // 2. We got the data from the buffer. + if (mBuffer.length() == mTransition.Size()) { + return SetTransition(aFunc(nextState, + mBuffer.begin(), + mBuffer.length())); + } + + // Anything else indicates a bug. + MOZ_ASSERT_UNREACHABLE("Unexpected state encountered during yield"); + return SetTransition(Transition::TerminateFailure()); + } + + template <typename Func> + Maybe<LexerResult> Truncated(SourceBufferIterator& aIterator, + Func aFunc) + { + // The data is truncated. Let the lexer clean up and decide which terminal + // state we should end up in. + LexerTransition<State> transition + = mTruncatedTransition.NextStateIsTerminal() + ? mTruncatedTransition + : aFunc(mTruncatedTransition.NextState(), nullptr, 0); + + if (!transition.NextStateIsTerminal()) { + MOZ_ASSERT_UNREACHABLE("Truncated state didn't lead to terminal state?"); + return SetTransition(Transition::TerminateFailure()); + } + + // If the SourceBuffer was completed with a failing state, we end in + // TerminalState::FAILURE no matter what. This only happens in exceptional + // situations like SourceBuffer itself encountering a failure due to OOM. + if (NS_FAILED(aIterator.CompletionStatus())) { + return SetTransition(Transition::TerminateFailure()); + } + + return SetTransition(transition); + } + + Maybe<LexerResult> SetTransition(const LexerTransition<State>& aTransition) + { + // There should be no transitions while we're buffering for a buffered read + // unless they're to terminal states. (The terminal state transitions would + // generally be triggered by error handling code.) + MOZ_ASSERT_IF(!mBuffer.empty(), + aTransition.NextStateIsTerminal() || + mBuffer.length() == mTransition.Size()); + + // Similarly, the only transitions allowed in the middle of an unbuffered + // read are to a terminal state, or a yield to the same state. Otherwise, we + // should remain in the same state until the unbuffered read completes. + MOZ_ASSERT_IF(mUnbufferedState, + aTransition.NextStateIsTerminal() || + (aTransition.ControlFlow() == ControlFlowStrategy::YIELD && + aTransition.NextState() == mTransition.UnbufferedState()) || + mUnbufferedState->mBytesRemaining == 0); + + // If this transition is a yield, save the next state and return. We'll + // handle the rest when Lex() gets called again. + if (!aTransition.NextStateIsTerminal() && + aTransition.ControlFlow() == ControlFlowStrategy::YIELD) { + mYieldingToState = Some(aTransition.NextState()); + return Some(LexerResult(Yield::OUTPUT_AVAILABLE)); + } + + // Update our transition. + mTransition = aTransition; + + // Get rid of anything left over from the previous state. + mBuffer.clear(); + mYieldingToState = Nothing(); + mUnbufferedState = Nothing(); + + // If we reached a terminal state, let the caller know. + if (mTransition.NextStateIsTerminal()) { + return Some(LexerResult(mTransition.NextStateAsTerminal())); + } + + // If we're entering an unbuffered state, record how long we'll stay in it. + if (mTransition.Buffering() == BufferingStrategy::UNBUFFERED) { + mUnbufferedState.emplace(mTransition.Size()); + } + + return Nothing(); // Keep processing. + } + + // State that tracks our position within an unbuffered read. + struct UnbufferedState + { + explicit UnbufferedState(size_t aBytesRemaining) + : mBytesRemaining(aBytesRemaining) + , mBytesConsumedInCurrentChunk(0) + { } + + size_t mBytesRemaining; + size_t mBytesConsumedInCurrentChunk; + }; + + Vector<char, InlineBufferSize> mBuffer; + LexerTransition<State> mTransition; + const LexerTransition<State> mTruncatedTransition; + Maybe<State> mYieldingToState; + Maybe<UnbufferedState> mUnbufferedState; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_StreamingLexer_h |