/* -*- 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 #include #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 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 class LexerTransition { public: // This is implicit so that Terminate{Success,Failure}() can return a // TerminalState and have it implicitly converted to a // LexerTransition, which avoids the need for a "" // qualification to the Terminate{Success,Failure}() callsite. MOZ_IMPLICIT LexerTransition(TerminalState aFinalState) : mNextState(aFinalState) {} bool NextStateIsTerminal() const { return mNextState.template is(); } TerminalState NextStateAsTerminal() const { return mNextState.template as(); } State NextState() const { return mNextState.template as().mState; } State UnbufferedState() const { return *mNextState.template as().mUnbufferedState; } size_t Size() const { return mNextState.template as().mSize; } BufferingStrategy Buffering() const { return mNextState.template as().mBufferingStrategy; } ControlFlowStrategy ControlFlow() const { return mNextState.template as().mControlFlowStrategy; } private: friend struct Transition; LexerTransition(State aNextState, const Maybe& aUnbufferedState, size_t aSize, BufferingStrategy aBufferingStrategy, ControlFlowStrategy aControlFlowStrategy) : mNextState(NonTerminalState(aNextState, aUnbufferedState, aSize, aBufferingStrategy, aControlFlowStrategy)) {} struct NonTerminalState { State mState; Maybe mUnbufferedState; size_t mSize; BufferingStrategy mBufferingStrategy; ControlFlowStrategy mControlFlowStrategy; NonTerminalState(State aState, const Maybe& 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 mNextState; }; struct Transition { /// Transition to @aNextState, buffering @aSize bytes of data. template static LexerTransition To(const State& aNextState, size_t aSize) { return LexerTransition(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 static LexerTransition ToAfterYield(const State& aNextState) { return LexerTransition(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 static LexerTransition ToUnbuffered(const State& aNextState, const State& aUnbufferedState, size_t aSize) { return LexerTransition(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 static LexerTransition ContinueUnbuffered(const State& aUnbufferedState) { return LexerTransition(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 static LexerTransition ContinueUnbufferedAfterYield(const State& aUnbufferedState, size_t aSize) { return LexerTransition(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 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, 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 class StreamingLexer { public: StreamingLexer(const LexerTransition& aStartState, const LexerTransition& 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); } /** * From the given SourceBufferIterator, aIterator, create a new iterator at * the same position, with the given read limit, aReadLimit. The read limit * applies after adjusting for the position. If the given iterator has been * advanced, but required buffering inside StreamingLexer, the position * of the cloned iterator will be at the beginning of buffered data; this * should match the perspective of the caller. */ SourceBufferIterator Clone(SourceBufferIterator& aIterator, size_t aReadLimit) const { // In order to advance to the current position of the iterator from the // perspective of the caller, we need to take into account if we are // buffering data. size_t pos = aIterator.Position(); if (!mBuffer.empty()) { pos += aIterator.Length(); MOZ_ASSERT(pos > mBuffer.length()); pos -= mBuffer.length(); } size_t readLimit = aReadLimit; if (aReadLimit != SIZE_MAX) { readLimit += pos; } SourceBufferIterator other = aIterator.Owner()->Iterator(readLimit); // Since the current iterator has already advanced to this point, we // know that the state can only be READY or COMPLETE. That does not mean // everything is stored in a single chunk, and may require multiple Advance // calls to get where we want to be. DebugOnly state; do { state = other.Advance(pos); MOZ_ASSERT(state != SourceBufferIterator::WAITING); MOZ_ASSERT(pos >= other.Length()); pos -= other.Length(); } while (pos > 0); // Force the data pointer to be where we expect it to be. state = other.Advance(0); MOZ_ASSERT(state != SourceBufferIterator::WAITING); return other; } template 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 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 Maybe 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 Maybe 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 Maybe 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 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 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 Maybe 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 Maybe 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 Maybe 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 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 SetTransition(const LexerTransition& 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 mBuffer; LexerTransition mTransition; const LexerTransition mTruncatedTransition; Maybe mYieldingToState; Maybe mUnbufferedState; }; } // namespace image } // namespace mozilla #endif // mozilla_image_StreamingLexer_h