// 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