Home Manual Reference Source

src/Tape.js

import eof from './eof.js';
import toAsyncIterator from './toAsyncIterator.js';

/**
 * Class that wraps a callable with a tape.
 * Do not use directly. Use {@link fromCallable} instead.
 */
export default class Tape {
	/**
	 * The constructor. Stores the callable that yields values to put on the tape.
	 *
	 * @param {Function} callable - The callable to use.
	 */
	constructor(callable) {
		/**
		 * The callable yielding values to put on the tape.
		 * @type {Function}
		 */
		this.callable = callable;

		/**
		 * Buffer used to implement {@link Tape#unread}.
		 * @type {Array}
		 */
		this.buffer = [];

		/**
		 * The eof symbol.
		 * @type {any}
		 */
		this.eof = eof;
	}

	/**
	 * Returns the next token on the tape or {@link Tape#eof}
	 * if the tape has been exhausted.
	 *
	 * @returns {Promise} The next token on the tape or {@link Tape#eof}.
	 */
	async read() {
		if (this.buffer.length > 0) return this.buffer.pop();

		const state = await this.callable();

		if (state.done) return this.eof;

		return state.value;
	}

	/**
	 * Puts a token back on the tape. If {@link Tape#read} is
	 * used just after, this token will be returned.
	 *
	 * @param {any} token - The token to put back on the tape.
	 */
	unread(token) {
		// Should this be async too ?
		this.buffer.push(token);
	}

	/**
	 * Skips the next token on the tape.
	 */
	async skip() {
		if (this.buffer.length > 0) this.buffer.pop();
		else await this.callable();
	}

	/**
	 * Skip the next `n` tokens on the tape.
	 *
	 * @param {number} n - The number of tokens to skip.
	 */
	async skipMany(n) {
		while (n-- > 0) await this.skip(); // eslint-disable-line no-await-in-loop
	}

	/**
	 * Returns an async iterator on the tokens of the tape.
	 *
	 * @example
	 * fromString('abc');
	 * for await ( const token of tape ) console.log(token) ;
	 *
	 * @returns {AsyncIterator} Iterator on the tokens of the tape.
	 */
	[Symbol.asyncIterator]() {
		return toAsyncIterator(this);
	}

	/**
	 * Explicitely throws when trying to access iterator symbol.
	 *
	 * @throws {Error} Always.
	 */
	[Symbol.iterator]() {
		throw new Error(
			'Not implemented. A tape has no *synchronous* iterator. Use `toArray` or `toString` instead.',
		);
	}
}