// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { TokenKind } from './Token';
import { TokenSequence } from './TokenSequence';
/**
 * Manages a stream of tokens that are read by the parser.
 *
 * @remarks
 * Use TokenReader.readToken() to read a token and advance the stream pointer.
 * Use TokenReader.peekToken() to preview the next token.
 * Use TokenReader.createMarker() and backtrackToMarker() to rewind to an earlier point.
 * Whenever readToken() is called, the token is added to an accumulated TokenSequence
 * that can be extracted by calling extractAccumulatedSequence().
 */
export class TokenReader {
    constructor(parserContext, embeddedTokenSequence) {
        this._parserContext = parserContext;
        this.tokens = parserContext.tokens;
        if (embeddedTokenSequence) {
            if (embeddedTokenSequence.parserContext !== this._parserContext) {
                throw new Error('The embeddedTokenSequence must use the same parser context');
            }
            this._readerStartIndex = embeddedTokenSequence.startIndex;
            this._readerEndIndex = embeddedTokenSequence.endIndex;
        }
        else {
            this._readerStartIndex = 0;
            this._readerEndIndex = this.tokens.length;
        }
        this._currentIndex = this._readerStartIndex;
        this._accumulatedStartIndex = this._readerStartIndex;
    }
    /**
     * Extracts and returns the TokenSequence that was accumulated so far by calls to readToken().
     * The next call to readToken() will start a new accumulated sequence.
     */
    extractAccumulatedSequence() {
        if (this._accumulatedStartIndex === this._currentIndex) {
            // If this happens, it indicates a parser bug:
            throw new Error('Parser assertion failed: The queue should not be empty when' +
                ' extractAccumulatedSequence() is called');
        }
        const sequence = new TokenSequence({
            parserContext: this._parserContext,
            startIndex: this._accumulatedStartIndex,
            endIndex: this._currentIndex
        });
        this._accumulatedStartIndex = this._currentIndex;
        return sequence;
    }
    /**
     * Returns true if the accumulated sequence has any tokens yet.  This will be false
     * when the TokenReader starts, and it will be false immediately after a call
     * to extractAccumulatedSequence().  Otherwise, it will become true whenever readToken()
     * is called.
     */
    isAccumulatedSequenceEmpty() {
        return this._accumulatedStartIndex === this._currentIndex;
    }
    /**
     * Like extractAccumulatedSequence(), but returns undefined if nothing has been
     * accumulated yet.
     */
    tryExtractAccumulatedSequence() {
        if (this.isAccumulatedSequenceEmpty()) {
            return undefined;
        }
        return this.extractAccumulatedSequence();
    }
    /**
     * Asserts that isAccumulatedSequenceEmpty() should return false.  If not, an exception
     * is throw indicating a parser bug.
     */
    assertAccumulatedSequenceIsEmpty() {
        if (!this.isAccumulatedSequenceEmpty()) {
            // If this happens, it indicates a parser bug:
            const sequence = new TokenSequence({
                parserContext: this._parserContext,
                startIndex: this._accumulatedStartIndex,
                endIndex: this._currentIndex
            });
            const tokenStrings = sequence.tokens.map((x) => x.toString());
            throw new Error('Parser assertion failed: The queue should be empty, but it contains:\n' +
                JSON.stringify(tokenStrings));
        }
    }
    /**
     * Returns the next token that would be returned by _readToken(), without
     * consuming anything.
     */
    peekToken() {
        return this.tokens[this._currentIndex];
    }
    /**
     * Returns the TokenKind for the next token that would be returned by _readToken(), without
     * consuming anything.
     */
    peekTokenKind() {
        if (this._currentIndex >= this._readerEndIndex) {
            return TokenKind.EndOfInput;
        }
        return this.tokens[this._currentIndex].kind;
    }
    /**
     * Like peekTokenKind(), but looks ahead two tokens.
     */
    peekTokenAfterKind() {
        if (this._currentIndex + 1 >= this._readerEndIndex) {
            return TokenKind.EndOfInput;
        }
        return this.tokens[this._currentIndex + 1].kind;
    }
    /**
     * Like peekTokenKind(), but looks ahead three tokens.
     */
    peekTokenAfterAfterKind() {
        if (this._currentIndex + 2 >= this._readerEndIndex) {
            return TokenKind.EndOfInput;
        }
        return this.tokens[this._currentIndex + 2].kind;
    }
    /**
     * Extract the next token from the input stream and return it.
     * The token will also be appended to the accumulated sequence, which can
     * later be accessed via extractAccumulatedSequence().
     */
    readToken() {
        if (this._currentIndex >= this._readerEndIndex) {
            // If this happens, it's a parser bug
            throw new Error('Cannot read past end of stream');
        }
        const token = this.tokens[this._currentIndex];
        if (token.kind === TokenKind.EndOfInput) {
            // We don't allow reading the EndOfInput token, because we want _peekToken()
            // to be always guaranteed to return a valid result.
            // If this happens, it's a parser bug
            throw new Error('The EndOfInput token cannot be read');
        }
        this._currentIndex++;
        return token;
    }
    /**
     * Returns the kind of the token immediately before the current token.
     */
    peekPreviousTokenKind() {
        if (this._currentIndex === 0) {
            return TokenKind.EndOfInput;
        }
        return this.tokens[this._currentIndex - 1].kind;
    }
    /**
     * Remembers the current position in the stream.
     */
    createMarker() {
        return this._currentIndex;
    }
    /**
     * Rewinds the stream pointer to a previous position in the stream.
     */
    backtrackToMarker(marker) {
        if (marker > this._currentIndex) {
            // If this happens, it's a parser bug
            throw new Error('The marker has expired');
        }
        this._currentIndex = marker;
        if (marker < this._accumulatedStartIndex) {
            this._accumulatedStartIndex = marker;
        }
    }
}
//# sourceMappingURL=TokenReader.js.map